mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-21 12:24:11 +00:00
Compare commits
106 Commits
simp_skip_
...
jhx/toArra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3fc1d1a08 | ||
|
|
5f59d7f7b4 | ||
|
|
1364157e91 | ||
|
|
a524fd4be8 | ||
|
|
de23226d0c | ||
|
|
550fa6994e | ||
|
|
f9e5f1f1fd | ||
|
|
6b0e7e1f46 | ||
|
|
9fb44fae29 | ||
|
|
1f4359cc80 | ||
|
|
8293fd4e09 | ||
|
|
2beb948a3b | ||
|
|
409c6cac4c | ||
|
|
ec39de8cae | ||
|
|
586c3f9140 | ||
|
|
feda615ed5 | ||
|
|
4f41ccfcbf | ||
|
|
e9f69d1068 | ||
|
|
5cc9f6f9cb | ||
|
|
09aa845940 | ||
|
|
73b87f2558 | ||
|
|
c0f264ffe0 | ||
|
|
52d0f715c3 | ||
|
|
ec30da8af7 | ||
|
|
27b7002138 | ||
|
|
a2ed4db562 | ||
|
|
628633d02e | ||
|
|
f8edf452de | ||
|
|
12dc171c48 | ||
|
|
42e6214a42 | ||
|
|
53af5ead53 | ||
|
|
b706c0064e | ||
|
|
8e1b51701b | ||
|
|
ad068824d0 | ||
|
|
7c4c57759d | ||
|
|
1118931516 | ||
|
|
7150638836 | ||
|
|
30693a2dae | ||
|
|
368ead54b2 | ||
|
|
7c10415cd8 | ||
|
|
b5122b6a7b | ||
|
|
8bc1a9c4ba | ||
|
|
4169cac51f | ||
|
|
c394a834c3 | ||
|
|
9069c538ad | ||
|
|
4e16eb0476 | ||
|
|
e924ef229c | ||
|
|
8012eedab5 | ||
|
|
33c53a2418 | ||
|
|
3b9b13b706 | ||
|
|
94d51b2321 | ||
|
|
0342d62109 | ||
|
|
4e5ce6b65d | ||
|
|
e11b320cd6 | ||
|
|
cb6bfefc7a | ||
|
|
25ea5f6fa1 | ||
|
|
4958404f37 | ||
|
|
3e11b5fe15 | ||
|
|
57bc058209 | ||
|
|
610fa69f15 | ||
|
|
3a9b594fc5 | ||
|
|
0bc8fe48e3 | ||
|
|
7350d0a3ff | ||
|
|
b376b1594e | ||
|
|
88801166b6 | ||
|
|
ad58deeae3 | ||
|
|
666d454b42 | ||
|
|
b7efd200f0 | ||
|
|
e83e467667 | ||
|
|
2efa9de78a | ||
|
|
25baf73005 | ||
|
|
0bd424b5e6 | ||
|
|
d841ef5eb5 | ||
|
|
188ff2dd20 | ||
|
|
7564b204ec | ||
|
|
6fd7350c7b | ||
|
|
7ed4d1c432 | ||
|
|
5f847c4ce3 | ||
|
|
090d158fb9 | ||
|
|
81ced3bd0f | ||
|
|
ab721c64b3 | ||
|
|
93369e8773 | ||
|
|
23f2314da7 | ||
|
|
8a23c294a4 | ||
|
|
a7a3ae13dd | ||
|
|
5edd59806c | ||
|
|
a2aadee28f | ||
|
|
923216f9a9 | ||
|
|
0f9702f4b4 | ||
|
|
df53e6c4cf | ||
|
|
916c97b625 | ||
|
|
439689b219 | ||
|
|
1d78712b6c | ||
|
|
39f716f902 | ||
|
|
22c8154811 | ||
|
|
05e9983e25 | ||
|
|
f51b356002 | ||
|
|
ec9570fdd0 | ||
|
|
b37fdea5bf | ||
|
|
29c245ceba | ||
|
|
b8b49c50b9 | ||
|
|
127b309a0d | ||
|
|
b7c3ff6e6d | ||
|
|
0aa2b83450 | ||
|
|
684f32fabe | ||
|
|
eefcbbb37b |
22
.github/workflows/actionlint.yml
vendored
Normal file
22
.github/workflows/actionlint.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
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@v3
|
||||
- name: actionlint
|
||||
uses: raven-actions/actionlint@v1
|
||||
with:
|
||||
pyflakes: false # we do not use python scripts
|
||||
46
.github/workflows/ci.yml
vendored
46
.github/workflows/ci.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
github.event_name == 'pull_request' && !contains( github.event.pull_request.labels.*.name, 'full-ci')
|
||||
}}
|
||||
run: |
|
||||
echo "quick=${{env.quick}}" >> $GITHUB_OUTPUT
|
||||
echo "quick=${{env.quick}}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Configure build matrix
|
||||
id: set-matrix
|
||||
@@ -124,10 +124,11 @@ jobs:
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"cross": true,
|
||||
"cross_target": "aarch64-apple-darwin",
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
"CMAKE_OPTIONS": "-DUSE_GMP=OFF -DLEAN_INSTALL_SUFFIX=-darwin_aarch64",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-apple-darwin.tar.zst https://github.com/leanprover/lean-llvm/releases/download/15.0.1/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-*",
|
||||
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm-aarch64-* lean-llvm-x86_64-*",
|
||||
"binary-check": "otool -L",
|
||||
"tar": "gtar" // https://github.com/actions/runner-images/issues/2619
|
||||
},
|
||||
@@ -151,9 +152,10 @@ jobs:
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"cross": true,
|
||||
"cross_target": "aarch64-unknown-linux-gnu",
|
||||
"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/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst https://github.com/leanprover/lean-llvm/releases/download/15.0.1/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-*"
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm-aarch64-* lean-llvm-x86_64-*"
|
||||
},
|
||||
{
|
||||
"name": "Linux 32bit",
|
||||
@@ -201,8 +203,8 @@ jobs:
|
||||
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 "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -210,7 +212,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'leanprover/lean4'
|
||||
id: set-release
|
||||
run: |
|
||||
TAG_NAME=${GITHUB_REF##*/}
|
||||
TAG_NAME="${GITHUB_REF##*/}"
|
||||
|
||||
# From https://github.com/fsaintjacques/semver-tool/blob/master/src/semver
|
||||
|
||||
@@ -227,11 +229,13 @@ jobs:
|
||||
|
||||
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]}" >> $GITHUB_OUTPUT
|
||||
echo "LEAN_VERSION_MINOR=${BASH_REMATCH[2]}" >> $GITHUB_OUTPUT
|
||||
echo "LEAN_VERSION_PATCH=${BASH_REMATCH[3]}" >> $GITHUB_OUTPUT
|
||||
echo "LEAN_SPECIAL_VERSION_DESC=${BASH_REMATCH[4]##-}" >> $GITHUB_OUTPUT
|
||||
echo "RELEASE_TAG=$TAG_NAME" >> $GITHUB_OUTPUT
|
||||
{
|
||||
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
|
||||
@@ -319,9 +323,15 @@ jobs:
|
||||
mkdir build
|
||||
cd build
|
||||
ulimit -c unlimited # coredumps
|
||||
# arguments passed to `cmake`
|
||||
# this also enables githash embedding into stage 1 library
|
||||
OPTIONS=(-DCHECK_OLEAN_VERSION=ON)
|
||||
OPTIONS+=(-DLEAN_EXTRA_MAKE_OPTS=-DwarningAsError=true)
|
||||
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 }})"
|
||||
@@ -405,7 +415,7 @@ jobs:
|
||||
- name: CCache stats
|
||||
run: ccache -s
|
||||
- name: Show stacktrace for coredumps
|
||||
if: ${{ failure() }} && matrix.os == 'ubuntu-latest'
|
||||
if: ${{ failure() && matrix.os == 'ubuntu-latest' }}
|
||||
run: |
|
||||
for c in coredumps/*; do
|
||||
progbin="$(file $c | sed "s/.*execfn: '\([^']*\)'.*/\1/")"
|
||||
@@ -413,7 +423,7 @@ jobs:
|
||||
done
|
||||
- name: Upload coredumps
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ failure() }} && matrix.os == 'ubuntu-latest'
|
||||
if: ${{ failure() && matrix.os == 'ubuntu-latest' }}
|
||||
with:
|
||||
name: coredumps-${{ matrix.name }}
|
||||
path: |
|
||||
@@ -480,16 +490,16 @@ jobs:
|
||||
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 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)
|
||||
last_tag="$(git log HEAD^ --simplify-by-decoration --pretty="format:%d" | grep -o "nightly-[-0-9]*" | head -n 1)"
|
||||
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@v1
|
||||
with:
|
||||
|
||||
8
.github/workflows/nix-ci.yml
vendored
8
.github/workflows/nix-ci.yml
vendored
@@ -90,6 +90,13 @@ jobs:
|
||||
# https://github.com/netlify/cli/issues/1809
|
||||
cp -r --dereference ./result ./dist
|
||||
if: matrix.name == 'Nix Linux'
|
||||
- name: Check manual for broken links
|
||||
id: lychee
|
||||
uses: lycheeverse/lychee-action@v1.9.0
|
||||
with:
|
||||
fail: true
|
||||
# gmplib.org consistently times out from GH actions
|
||||
args: --base './dist' --no-progress --exclude 'gmplib.org' './dist/**/*.html'
|
||||
- name: Push to Cachix
|
||||
run: |
|
||||
[ -z "${{ secrets.CACHIX_AUTH_TOKEN }}" ] || cachix push -j4 lean4 ./push-* || true
|
||||
@@ -112,6 +119,7 @@ jobs:
|
||||
echo "message=`git log -1 --pretty=format:"%s"`" >> "$GITHUB_OUTPUT"
|
||||
- name: Publish manual to Netlify
|
||||
uses: nwtgck/actions-netlify@v2.0
|
||||
id: publish-manual
|
||||
with:
|
||||
publish-dir: ./dist
|
||||
production-branch: master
|
||||
|
||||
171
.github/workflows/pr-release.yml
vendored
171
.github/workflows/pr-release.yml
vendored
@@ -6,6 +6,10 @@
|
||||
# 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:
|
||||
@@ -37,7 +41,7 @@ jobs:
|
||||
name: build-.*
|
||||
name_is_regexp: true
|
||||
|
||||
- name: Push branch and tag
|
||||
- name: Push tag
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
run: |
|
||||
git init --bare lean4.git
|
||||
@@ -69,6 +73,20 @@ jobs:
|
||||
# The token used here must have `workflow` privileges.
|
||||
GITHUB_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
|
||||
- name: Report release status
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: actions/github-script@v6
|
||||
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: Add label
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: actions/github-script@v7
|
||||
@@ -89,7 +107,7 @@ jobs:
|
||||
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
|
||||
| sort -rV | head -n 1 | sed "s/^nightly-*/MOST_RECENT_NIGHTLY=/" | tee -a "$GITHUB_ENV"
|
||||
|
||||
- name: 'Setup jq'
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
@@ -100,22 +118,32 @@ jobs:
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
id: ready
|
||||
run: |
|
||||
echo "Most recent nightly in your branch: $MOST_RECENT_NIGHTLY"
|
||||
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: $NIGHTLY_SHA"
|
||||
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 "Most recent nightly tag agrees with the merge base."
|
||||
echo "The merge base of this PR coincides with the nightly release"
|
||||
|
||||
REMOTE_BRANCHES=$(git ls-remote -h https://github.com/leanprover-community/mathlib4.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 "$REMOTE_BRANCHES" ]]; then
|
||||
echo "... and Mathlib has a 'nightly-testing-$MOST_RECENT_NIGHTLY' branch."
|
||||
if [[ -n "$MATHLIB_REMOTE_TAGS" ]]; then
|
||||
echo "... and Mathlib has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE=""
|
||||
else
|
||||
echo "... but Mathlib does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' branch."
|
||||
MESSAGE="- ❗ Mathlib CI can not be attempted yet, as the 'nightly-testing-$MOST_RECENT_NIGHTLY' branch does not exist there yet. We will retry when you push more commits. It may be necessary to rebase onto 'nightly' tomorrow."
|
||||
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
|
||||
|
||||
STD_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover/std4.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
|
||||
|
||||
if [[ -n "$STD_REMOTE_TAGS" ]]; then
|
||||
echo "... and Std has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE=""
|
||||
else
|
||||
echo "... but Std does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE="- ❗ Std 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\`, Std CI should run now."
|
||||
fi
|
||||
|
||||
else
|
||||
@@ -123,20 +151,24 @@ jobs:
|
||||
echo "but 'git merge-base origin/master HEAD' reported: $MERGE_BASE_SHA"
|
||||
git -C lean4.git log -10 origin/master
|
||||
|
||||
MESSAGE="- ❗ Mathlib CI will not be attempted unless you rebase your PR onto the 'nightly' branch."
|
||||
MESSAGE="- ❗ Std/Mathlib CI will not be attempted unless your PR branches off the \`nightly-with-mathlib\` branch."
|
||||
fi
|
||||
|
||||
if [[ -n "$MESSAGE" ]]; then
|
||||
|
||||
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 -L -s -H "Authorization: token ${{ secrets.MATHLIB4_BOT }}" \
|
||||
existing_comment="$(curl -L -s -H "Authorization: token ${{ secrets.MATHLIB4_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments" \
|
||||
| jq '.[] | select(.body | startswith("- ❗ Mathlib") or startswith("- ✅ Mathlib") or startswith("- ❌ Mathlib") or startswith("- 💥 Mathlib") or startswith("- 🟡 Mathlib"))')
|
||||
existing_comment_id=$(echo "$existing_comment" | jq -r .id)
|
||||
existing_comment_body=$(echo "$existing_comment" | jq -r .body)
|
||||
| jq 'first(.[] | select(.body | test("^- . Mathlib") or startswith("Mathlib CI status")) | select(.user.login == "leanprover-community-mathlib4-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"))"
|
||||
@@ -146,13 +178,14 @@ jobs:
|
||||
# Append new result to the existing comment or post a new comment
|
||||
# It's essential we use the MATHLIB4_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_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-d "$(jq --null-input --arg val "$MESSAGE" '{"body": $val}')" \
|
||||
-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
|
||||
@@ -167,18 +200,93 @@ jobs:
|
||||
else
|
||||
echo "The message already exists in the comment body."
|
||||
fi
|
||||
echo "mathlib_ready=false" >> $GITHUB_OUTPUT
|
||||
echo "mathlib_ready=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "mathlib_ready=true" >> $GITHUB_OUTPUT
|
||||
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@v6
|
||||
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 Std branch using this toolchain.
|
||||
# Std 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 Std fails.
|
||||
- name: Cleanup workspace
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
run: |
|
||||
sudo rm -rf ./*
|
||||
|
||||
# Checkout the Std repository with all branches
|
||||
- name: Checkout Std repository
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: leanprover/std4
|
||||
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_std_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 "This shouldn't be possible: couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' tag at Std. 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 }}" > 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, pushing an empty commit."
|
||||
git switch lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# The Std `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
|
||||
git commit --allow-empty -m "Trigger CI 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 *
|
||||
sudo rm -rf ./*
|
||||
|
||||
# Checkout the mathlib4 repository with all branches
|
||||
- name: Checkout mathlib4 repository
|
||||
@@ -190,37 +298,38 @@ jobs:
|
||||
ref: nightly-testing
|
||||
fetch-depth: 0 # This ensures we check out all tags and branches.
|
||||
|
||||
- name: Check if branch exists
|
||||
- name: Check if tag exists
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
id: check_branch
|
||||
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 branch -r | grep -q "nightly-testing-${MOST_RECENT_NIGHTLY}"; then
|
||||
BASE=nightly-testing-${MOST_RECENT_NIGHTLY}
|
||||
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 "This shouldn't be possible: couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' branch at Mathlib. Falling back to 'nightly-testing'."
|
||||
BASE=nightly-testing
|
||||
fi
|
||||
|
||||
echo "Using base branch: $BASE"
|
||||
echo "Using base tag: $BASE"
|
||||
|
||||
git checkout $BASE
|
||||
|
||||
EXISTS=$(git ls-remote --heads origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} | wc -l)
|
||||
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 checkout -b lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
git switch -c lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} "$BASE"
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
sed -i "s/require std from git \"https:\/\/github.com\/leanprover\/std4\" @ \".\+\"/require std from git \"https:\/\/github.com\/leanprover\/std4\" @ \"nightly-testing-${MOST_RECENT_NIGHTLY}\"/" lakefile.lean
|
||||
git add lakefile.lean
|
||||
git commit -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
else
|
||||
echo "Branch already exists, pushing an empty commit."
|
||||
git checkout lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# The Mathlib `nightly-testing` or `nightly-testing-YYYY-MM-DD` branch may have moved since this branch was created, so merge their changes.
|
||||
git merge $BASE --strategy-option ours --no-commit --allow-unrelated-histories
|
||||
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
|
||||
git commit --allow-empty -m "Trigger CI for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
This is the repository for **Lean 4**.
|
||||
|
||||
We provide [nightly releases](https://github.com/leanprover/lean4-nightly/releases)
|
||||
and have just begun regular [stable point releases](https://github.com/leanprover/lean4/releases).
|
||||
|
||||
# About
|
||||
|
||||
- [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)
|
||||
- [Quickstart](https://lean-lang.org/lean4/doc/quickstart.html)
|
||||
- [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/)
|
||||
|
||||
204
RELEASES.md
204
RELEASES.md
@@ -11,6 +11,192 @@ of each version.
|
||||
v4.6.0 (development in progress)
|
||||
---------
|
||||
|
||||
* Add custom simplification procedures (aka `simproc`s) to `simp`. Simprocs can be triggered by the simplifier on a specified term-pattern. Here is an small example:
|
||||
```lean
|
||||
import Lean.Meta.Tactic.Simp.BuiltinSimprocs.Nat
|
||||
|
||||
def foo (x : Nat) : Nat :=
|
||||
x + 10
|
||||
|
||||
/--
|
||||
The `simproc` `reduceFoo` is invoked on terms that match the pattern `foo _`.
|
||||
-/
|
||||
simproc reduceFoo (foo _) :=
|
||||
/- A term of type `Expr → SimpM (Option Step) -/
|
||||
fun e => OptionT.run do
|
||||
/- `simp` uses matching modulo reducibility. So, we ensure the term is a `foo`-application. -/
|
||||
guard (e.isAppOfArity ``foo 1)
|
||||
/- `Nat.fromExpr?` tries to convert an expression into a `Nat` value -/
|
||||
let n ← Nat.fromExpr? e.appArg!
|
||||
/-
|
||||
The `Step` type has two constructors: `.done` and `.visit`.
|
||||
* The constructor `.done` instructs `simp` that the result does
|
||||
not need to be simplied further.
|
||||
* The constructor `.visit` instructs `simp` to visit the resulting expression.
|
||||
|
||||
If the result holds definitionally as in this example, the field `proof?` can be omitted.
|
||||
-/
|
||||
return .done { expr := Lean.mkNatLit (n+10) }
|
||||
```
|
||||
We disable simprocs support by using the command `set_option simprocs false`. This command is particularly useful when porting files to v4.6.0.
|
||||
Simprocs can be scoped, manually added to `simp` commands, and suppressed using `-`. They are also supported by `simp?`. `simp only` does not execute any `simproc`. Here are some examples for the `simproc` defined above.
|
||||
```lean
|
||||
example : x + foo 2 = 12 + x := by
|
||||
set_option simprocs false in
|
||||
/- This `simp` command does not make progress since `simproc`s are disabled. -/
|
||||
fail_if_success simp
|
||||
simp_arith
|
||||
|
||||
example : x + foo 2 = 12 + x := by
|
||||
/- `simp only` must not use the default simproc set. -/
|
||||
fail_if_success simp only
|
||||
simp_arith
|
||||
|
||||
example : x + foo 2 = 12 + x := by
|
||||
/-
|
||||
`simp only` does not use the default simproc set,
|
||||
but we can provide simprocs as arguments. -/
|
||||
simp only [reduceFoo]
|
||||
simp_arith
|
||||
|
||||
example : x + foo 2 = 12 + x := by
|
||||
/- We can use `-` to disable `simproc`s. -/
|
||||
fail_if_success simp [-reduceFoo]
|
||||
simp_arith
|
||||
```
|
||||
|
||||
* The syntax of the `termination_by` and `decreasing_by` termination hints is overhauled:
|
||||
|
||||
* They are now placed directly after the function they apply to, instead of
|
||||
after the whole `mutual` block.
|
||||
* Therefore, the function name no longer has to be mentioned in the hint.
|
||||
* If the function has a `where` clause, the `termination_by` and
|
||||
`decreasing_by` for that function come before the `where`. The
|
||||
functions in the `where` clause can have their own termination hints, each
|
||||
following the corresponding definition.
|
||||
* The `termination_by` clause can only bind “extra parameters”, that are not
|
||||
already bound by the function header, but are bound in a lambda (`:= fun x
|
||||
y z =>`) or in patterns (`| x, n + 1 => …`). These extra parameters used to
|
||||
be understood as a suffix of the function parameters; now it is a prefix.
|
||||
|
||||
Migration guide: In simple cases just remove the function name, and any
|
||||
variables already bound at the header.
|
||||
```diff
|
||||
def foo : Nat → Nat → Nat := …
|
||||
-termination_by foo a b => a - b
|
||||
+termination_by a b => a - b
|
||||
```
|
||||
or
|
||||
```diff
|
||||
def foo : Nat → Nat → Nat := …
|
||||
-termination_by _ a b => a - b
|
||||
+termination_by a b => a - b
|
||||
```
|
||||
|
||||
If the parameters are bound in the function header (before the `:`), remove them as well:
|
||||
```diff
|
||||
def foo (a b : Nat) : Nat := …
|
||||
-termination_by foo a b => a - b
|
||||
+termination_by a - b
|
||||
```
|
||||
|
||||
Else, if there are multiple extra parameters, make sure to refer to the right
|
||||
ones; the bound variables are interpreted from left to right, no longer from
|
||||
right to left:
|
||||
```diff
|
||||
def foo : Nat → Nat → Nat → Nat
|
||||
| a, b, c => …
|
||||
-termination_by foo b c => b
|
||||
+termination_by a b => b
|
||||
```
|
||||
|
||||
In the case of a `mutual` block, place the termination arguments (without the
|
||||
function name) next to the function definition:
|
||||
```diff
|
||||
-mutual
|
||||
-def foo : Nat → Nat → Nat := …
|
||||
-def bar : Nat → Nat := …
|
||||
-end
|
||||
-termination_by
|
||||
- foo a b => a - b
|
||||
- bar a => a
|
||||
+mutual
|
||||
+def foo : Nat → Nat → Nat := …
|
||||
+termination_by a b => a - b
|
||||
+def bar : Nat → Nat := …
|
||||
+termination_by a => a
|
||||
+end
|
||||
```
|
||||
|
||||
Similarly, if you have (mutual) recursion through `where` or `let rec`, the
|
||||
termination hints are now placed directly after the function they apply to:
|
||||
```diff
|
||||
-def foo (a b : Nat) : Nat := …
|
||||
- where bar (x : Nat) : Nat := …
|
||||
-termination_by
|
||||
- foo a b => a - b
|
||||
- bar x => x
|
||||
+def foo (a b : Nat) : Nat := …
|
||||
+termination_by a - b
|
||||
+ where
|
||||
+ bar (x : Nat) : Nat := …
|
||||
+ termination_by x
|
||||
|
||||
-def foo (a b : Nat) : Nat :=
|
||||
- let rec bar (x : Nat) : Nat := …
|
||||
- …
|
||||
-termination_by
|
||||
- foo a b => a - b
|
||||
- bar x => x
|
||||
+def foo (a b : Nat) : Nat :=
|
||||
+ let rec bar (x : Nat) : Nat := …
|
||||
+ termination_by x
|
||||
+ …
|
||||
+termination_by a - b
|
||||
```
|
||||
|
||||
In cases where a single `decreasing_by` clause applied to multiple mutually
|
||||
recursive functions before, the tactic now has to be duplicated.
|
||||
|
||||
* The semantics of `decreasing_by` changed; the tactic is applied to all
|
||||
termination proof goals together, not individually.
|
||||
|
||||
This helps when writing termination proofs interactively, as one can focus
|
||||
each subgoal individually, for example using `·`. Previously, the given
|
||||
tactic script had to work for _all_ goals, and one had to resort to tactic
|
||||
combinators like `first`:
|
||||
|
||||
```diff
|
||||
def foo (n : Nat) := … foo e1 … foo e2 …
|
||||
-decreasing_by
|
||||
-simp_wf
|
||||
-first | apply something_about_e1; …
|
||||
- | apply something_about_e2; …
|
||||
+decreasing_by
|
||||
+all_goals simp_wf
|
||||
+· apply something_about_e1; …
|
||||
+· apply something_about_e2; …
|
||||
```
|
||||
|
||||
To obtain the old behaviour of applying a tactic to each goal individually,
|
||||
use `all_goals`:
|
||||
```diff
|
||||
def foo (n : Nat) := …
|
||||
-decreasing_by some_tactic
|
||||
+decreasing_by all_goals some_tactic
|
||||
```
|
||||
|
||||
In the case of mutual recursion each `decreasing_by` now applies to just its
|
||||
function. If some functions in a recursive group do not have their own
|
||||
`decreasing_by`, the default `decreasing_tactic` is used. If the same tactic
|
||||
ought to be applied to multiple functions, the `decreasing_by` clause has to
|
||||
be repeated at each of these functions.
|
||||
|
||||
* Modify `InfoTree.context` to facilitate augmenting it with partial contexts while elaborating a command. This breaks backwards compatibility with all downstream projects that traverse the `InfoTree` manually instead of going through the functions in `InfoUtils.lean`, as well as those manually creating and saving `InfoTree`s. See [PR #3159](https://github.com/leanprover/lean4/pull/3159) for how to migrate your code.
|
||||
|
||||
* Add language server support for [call hierarchy requests](https://www.youtube.com/watch?v=r5LA7ivUb2c) ([PR #3082](https://github.com/leanprover/lean4/pull/3082)). The change to the .ilean format in this PR means that projects must be fully rebuilt once in order to generate .ilean files with the new format before features like "find references" work correctly again.
|
||||
|
||||
|
||||
v4.5.0
|
||||
---------
|
||||
|
||||
@@ -33,7 +219,7 @@ v4.5.0
|
||||
Migration guide: Use `termination_by` instead, e.g.:
|
||||
```diff
|
||||
-termination_by' measure (fun ⟨i, _⟩ => as.size - i)
|
||||
+termination_by go i _ => as.size - i
|
||||
+termination_by i _ => as.size - i
|
||||
```
|
||||
|
||||
If the well-founded relation you want to use is not the one that the
|
||||
@@ -41,7 +227,7 @@ v4.5.0
|
||||
you can use `WellFounded.wrap` from the std libarary to explicitly give one:
|
||||
```diff
|
||||
-termination_by' ⟨r, hwf⟩
|
||||
+termination_by _ x => hwf.wrap x
|
||||
+termination_by x => hwf.wrap x
|
||||
```
|
||||
|
||||
* Support snippet edits in LSP `TextEdit`s. See `Lean.Lsp.SnippetString` for more details.
|
||||
@@ -50,7 +236,7 @@ v4.5.0
|
||||
- `Widget.UserWidgetDefinition` is deprecated in favour of `Widget.Module`. The annotation `@[widget]` is deprecated in favour of `@[widget_module]`. To migrate a definition of type `UserWidgetDefinition`, remove the `name` field and replace the type with `Widget.Module`. Removing the `name` results in a title bar no longer being drawn above your panel widget. To add it back, draw it as part of the component using `<details open=true><summary class='mv2 pointer'>{name}</summary>{rest_of_widget}</details>`. See an example migration [here](https://github.com/leanprover/std4/pull/475/files#diff-857376079661a0c28a53b7ff84701afabbdf529836a6944d106c5294f0e68109R43-R83).
|
||||
- The new command `show_panel_widgets` allows displaying always-on and locally-on panel widgets.
|
||||
- `RpcEncodable` widget props can now be stored in the infotree.
|
||||
- See [RFC 2963](https://github.com/leanprover/lean4/issues/2963) for more details and motivation.
|
||||
- See [RFC 2963](https://github.com/leanprover/lean4/issues/2963) for more details and motivation.
|
||||
|
||||
* If no usable lexicographic order can be found automatically for a termination proof, explain why.
|
||||
See [feat: GuessLex: if no measure is found, explain why](https://github.com/leanprover/lean4/pull/2960).
|
||||
@@ -71,7 +257,7 @@ v4.5.0
|
||||
* Tactics with `withLocation *` [no longer fail](https://github.com/leanprover/lean4/pull/2917) if they close the main goal.
|
||||
|
||||
* Implementation of a `test_extern` command for writing tests for `@[extern]` and `@[implemented_by]` functions.
|
||||
Usage is
|
||||
Usage is
|
||||
```
|
||||
import Lean.Util.TestExtern
|
||||
|
||||
@@ -79,8 +265,8 @@ v4.5.0
|
||||
```
|
||||
The head symbol must be the constant with the `@[extern]` or `@[implemented_by]` attribute. The return type must have a `DecidableEq` instance.
|
||||
|
||||
Bug fixes for
|
||||
[#2853](https://github.com/leanprover/lean4/issues/2853), [#2953](https://github.com/leanprover/lean4/issues/2953), [#2966](https://github.com/leanprover/lean4/issues/2966),
|
||||
Bug fixes for
|
||||
[#2853](https://github.com/leanprover/lean4/issues/2853), [#2953](https://github.com/leanprover/lean4/issues/2953), [#2966](https://github.com/leanprover/lean4/issues/2966),
|
||||
[#2971](https://github.com/leanprover/lean4/issues/2971), [#2990](https://github.com/leanprover/lean4/issues/2990), [#3094](https://github.com/leanprover/lean4/issues/3094).
|
||||
|
||||
Bug fix for [eager evaluation of default value](https://github.com/leanprover/lean4/pull/3043) in `Option.getD`.
|
||||
@@ -93,19 +279,19 @@ v4.4.0
|
||||
---------
|
||||
|
||||
* Lake and the language server now support per-package server options using the `moreServerOptions` config field, as well as options that apply to both the language server and `lean` using the `leanOptions` config field. Setting either of these fields instead of `moreServerArgs` ensures that viewing files from a dependency uses the options for that dependency. Additionally, `moreServerArgs` is being deprecated in favor of the `moreGlobalServerArgs` field. See PR [#2858](https://github.com/leanprover/lean4/pull/2858).
|
||||
|
||||
|
||||
A Lakefile with the following deprecated package declaration:
|
||||
```lean
|
||||
def moreServerArgs := #[
|
||||
"-Dpp.unicode.fun=true"
|
||||
]
|
||||
def moreLeanArgs := moreServerArgs
|
||||
|
||||
|
||||
package SomePackage where
|
||||
moreServerArgs := moreServerArgs
|
||||
moreLeanArgs := moreLeanArgs
|
||||
```
|
||||
|
||||
|
||||
... can be updated to the following package declaration to use per-package options:
|
||||
```lean
|
||||
package SomePackage where
|
||||
|
||||
@@ -483,7 +483,43 @@ def baz : Char → Nat
|
||||
| _ => 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.
|
||||
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.
|
||||
|
||||
```lean
|
||||
universe u
|
||||
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector α 0
|
||||
| cons : α → Vector α n → Vector α (n+1)
|
||||
|
||||
namespace Vector
|
||||
|
||||
def head : Vector α (n+1) → α
|
||||
| cons h t => h
|
||||
|
||||
def tail : Vector α (n+1) → Vector α n
|
||||
| cons h t => t
|
||||
|
||||
def map (f : α → β → γ) : Vector α n → Vector β n → Vector γ n
|
||||
| nil, nil => nil
|
||||
| cons a va, cons b vb => cons (f a b) (map f va vb)
|
||||
|
||||
end Vector
|
||||
```
|
||||
|
||||
.. _recursive_functions:
|
||||
|
||||
Recursive functions
|
||||
===================
|
||||
|
||||
Lean must ensure that a recursive function terminates, for which there are two strategies: _structural recursion_, in which all recursive calls are made on smaller parts of the input data, and _well-founded recursion_, in which recursive calls are justified by showing that arguments to recursive calls are smaller according to some other measure.
|
||||
|
||||
Structural recursion
|
||||
--------------------
|
||||
|
||||
If the definition of a function contains recursive calls, Lean first 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.
|
||||
|
||||
```lean
|
||||
namespace Hide
|
||||
@@ -504,7 +540,12 @@ example : append [(1 : Nat), 2, 3] [4, 5] = [1, 2, 3, 4, 5] => rfl
|
||||
end Hide
|
||||
```
|
||||
|
||||
If structural recursion fails, the equation compiler falls back on well-founded recursion. It tries to infer an instance of ``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``.
|
||||
Well-founded recursion
|
||||
---------------------
|
||||
|
||||
If structural recursion fails, the equation compiler falls back on well-founded recursion. It tries to infer an instance of ``SizeOf`` for the type of each argument, and then tries to find a permutation of the arguments such that each recursive call is decreasing under the lexicographic order with respect to ``sizeOf`` measures. 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 the case of well-founded recursion, the equation used to declare the function holds only propositionally, but not definitionally, and can be accessed using ``unfold``, ``simp`` and ``rewrite`` with the function name (for example ``unfold foo`` or ``simp [foo]``, where ``foo`` is the function defined with well-founded recursion).
|
||||
|
||||
```lean
|
||||
namespace Hide
|
||||
@@ -528,9 +569,53 @@ by rw [div]; rfl
|
||||
end Hide
|
||||
```
|
||||
|
||||
If Lean cannot find a permutation of the arguments for which all recursive calls are decreasing, it will print a table that contains, for every recursive call, which arguments Lean could prove to be decreasing. For example, a function with three recursive calls and four parameters might cause the following message to be printed
|
||||
|
||||
```
|
||||
example.lean:37:0-43:31: error: Could not find a decreasing measure.
|
||||
The arguments relate at each recursive call as follows:
|
||||
(<, ≤, =: relation proved, ? all proofs failed, _: no proof attempted)
|
||||
x1 x2 x3 x4
|
||||
1) 39:6-27 = = _ =
|
||||
2) 40:6-25 = ? _ <
|
||||
3) 41:6-25 < _ _ _
|
||||
Please use `termination_by` to specify a decreasing measure.
|
||||
```
|
||||
|
||||
This table should be read as follows:
|
||||
|
||||
* In the first recursive call, in line 39, arguments 1, 2 and 4 are equal to the function's parameters.
|
||||
* The second recursive call, in line 40, has an equal first argument, a smaller fourth argument, and nothing could be inferred for the second argument.
|
||||
* The third recursive call, in line 41, has a decreasing first argument.
|
||||
* No other proofs were attempted, either because the parameter has a type without a non-trivial ``WellFounded`` instance (parameter 3), or because it is already clear that no decreasing measure can be found.
|
||||
|
||||
|
||||
Lean will print the termination argument it found if ``set_option showInferredTerminationBy true`` is set.
|
||||
|
||||
If Lean does not find the termination argument, or if you want to be explicit, you can append a `termination_by` clause to the function definition, after the function's body, but before the `where` clause if present. It is of the form
|
||||
```
|
||||
termination_by e
|
||||
```
|
||||
where ``e`` is an expression that depends on the parameters of the function and should be decreasing at each recursive call. The type of `e` should be an instance of the class ``WellFoundedRelation``, which determines how to compare two values of that type.
|
||||
|
||||
If ``f`` has parameters “after the ``:``” (for example when defining functions via patterns using `|`), then these can be brought into scope using the syntax
|
||||
```
|
||||
termination_by a₁ … aₙ => e
|
||||
```
|
||||
|
||||
By default, Lean uses the tactic ``decreasing_tactic`` when proving that an argument is decreasing; see its documentation for how to globally extend it. You can also choose to use a different tactic for a given function definition with the clause
|
||||
```
|
||||
decreasing_by <tac>
|
||||
```
|
||||
which should come after ``termination_by`, if present.
|
||||
|
||||
|
||||
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.
|
||||
Mutual recursion
|
||||
----------------
|
||||
|
||||
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). Mutual definitions are always compiled using well-founded recursion, and so once again the defining equations hold only propositionally.
|
||||
|
||||
```lean
|
||||
mutual
|
||||
@@ -587,29 +672,31 @@ def num_consts_lst : List Term → Nat
|
||||
end
|
||||
```
|
||||
|
||||
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.
|
||||
In a set of mutually recursive function, either all or no functions must have an explicit termination argument (``termination_by``). A change of the default termination tactic (``decreasing_by``) only affects the proofs about the recursive calls of that function, not the other functions in the group.
|
||||
|
||||
```lean
|
||||
universe u
|
||||
```
|
||||
mutual
|
||||
theorem even_of_odd_succ : ∀ n, Odd (n + 1) → Even n
|
||||
| _, odd_succ n h => h
|
||||
termination_by n h => h
|
||||
decreasing_by decreasing_tactic
|
||||
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector α 0
|
||||
| cons : α → Vector α n → Vector α (n+1)
|
||||
theorem odd_of_even_succ : ∀ n, Even (n + 1) → Odd n
|
||||
| _, even_succ n h => h
|
||||
termination_by n h => h
|
||||
end
|
||||
```
|
||||
|
||||
namespace Vector
|
||||
Another way to express mutual recursion is using local function definitions in ``where`` or ``let rec`` clauses: these can be mutually recursive with each other and their containing function:
|
||||
|
||||
def head {α : Type} : Vector α (n+1) → α
|
||||
| cons h t => h
|
||||
|
||||
def tail {α : Type} : Vector α (n+1) → Vector α 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 f va vb)
|
||||
|
||||
end Vector
|
||||
```
|
||||
theorem even_of_odd_succ : ∀ n, Odd (n + 1) → Even n
|
||||
| _, odd_succ n h => h
|
||||
termination_by n h => h
|
||||
where
|
||||
theorem odd_of_even_succ : ∀ n, Even (n + 1) → Odd n
|
||||
| _, even_succ n h => h
|
||||
termination_by n h => h
|
||||
```
|
||||
|
||||
.. _match_expressions:
|
||||
|
||||
@@ -82,7 +82,7 @@ theorem List.palindrome_ind (motive : List α → Prop)
|
||||
have ih := palindrome_ind motive h₁ h₂ h₃ (a₂::as').dropLast
|
||||
have : [a₁] ++ (a₂::as').dropLast ++ [(a₂::as').last (by simp)] = a₁::a₂::as' := by simp
|
||||
this ▸ h₃ _ _ _ ih
|
||||
termination_by _ as => as.length
|
||||
termination_by as.length
|
||||
|
||||
/-!
|
||||
We use our new induction principle to prove that if `as.reverse = as`, then `Palindrome as` holds.
|
||||
|
||||
9
doc/flake.lock
generated
9
doc/flake.lock
generated
@@ -69,15 +69,16 @@
|
||||
"leanInk": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1666154782,
|
||||
"narHash": "sha256-0ELqEca6jZT4BW/mqkDD+uYuxW5QlZUFlNwZkvugsg8=",
|
||||
"owner": "digama0",
|
||||
"lastModified": 1704976501,
|
||||
"narHash": "sha256-FSBUsbX0HxakSnYRYzRBDN2YKmH9EkA0q9p7TSPEJTI=",
|
||||
"owner": "leanprover",
|
||||
"repo": "LeanInk",
|
||||
"rev": "12a2aec9b5f4aa84e84fb01a9af1da00d8aaff4e",
|
||||
"rev": "51821e3c2c032c88e4b2956483899d373ec090c4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "leanprover",
|
||||
"ref": "refs/pull/57/merge",
|
||||
"repo": "LeanInk",
|
||||
"type": "github"
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
flake = false;
|
||||
};
|
||||
inputs.leanInk = {
|
||||
url = "github:leanprover/LeanInk";
|
||||
url = "github:leanprover/LeanInk/refs/pull/57/merge";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ def fact x :=
|
||||
|
||||
#eval fact 100
|
||||
```
|
||||
By default, Lean only accepts total functions. The `partial` keyword should be used when Lean cannot
|
||||
establish that a function always terminates.
|
||||
By default, Lean only accepts total functions.
|
||||
The `partial` keyword may be used to define a recursive function without a termination proof; `partial` functions compute in compiled programs, but are opaque in proofs and during type checking.
|
||||
```lean
|
||||
partial def g (x : Nat) (p : Nat -> Bool) : Nat :=
|
||||
if p x then
|
||||
|
||||
@@ -10,7 +10,6 @@ Platform-Specific Setup
|
||||
|
||||
- [Linux (Ubuntu)](ubuntu.md)
|
||||
- [Windows (msys2)](msys2.md)
|
||||
- [Windows (Visual Studio)](msvc.md)
|
||||
- [Windows (WSL)](wsl.md)
|
||||
- [macOS (homebrew)](osx-10.9.md)
|
||||
- Linux/macOS/WSL via [Nix](https://nixos.org/nix/): Call `nix-shell` in the project root. That's it.
|
||||
|
||||
@@ -60,7 +60,7 @@ While parsing `a * (b + c)`, `(b + c)` is assigned a precedence `60` by the addi
|
||||
the right argument to have precedence **at least** 71. Thus, this parse is invalid. In contrast, `(a * b) + c` assigns
|
||||
a precedence of `70` to `(a * b)`. This is compatible with addition which expects the left argument to have precedence
|
||||
**at least `60` ** (`70` is greater than `60`). Thus, the string `a * b + c` is parsed as `(a * b) + c`.
|
||||
For more details, please look at the [Lean manual on syntax extensions](../syntax.md#notations-and-precedence).
|
||||
For more details, please look at the [Lean manual on syntax extensions](./notation.md#notations-and-precedence).
|
||||
|
||||
To go from strings into `Arith`, we define a macro to
|
||||
translate the syntax category `arith` into an `Arith` inductive value that
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
### Tier 1
|
||||
|
||||
Platforms built & tested by our CI, available as nightly releases via elan (see below)
|
||||
Platforms built & tested by our CI, available as binary releases via elan (see below)
|
||||
|
||||
* x86-64 Linux with glibc 2.27+
|
||||
* x86-64 macOS 10.15+
|
||||
@@ -10,7 +10,7 @@ Platforms built & tested by our CI, available as nightly releases via elan (see
|
||||
|
||||
### Tier 2
|
||||
|
||||
Platforms cross-compiled but not tested by our CI, available as nightly releases
|
||||
Platforms cross-compiled but not tested by our CI, available as binary releases
|
||||
|
||||
Releases may be silently broken due to the lack of automated testing.
|
||||
Issue reports and fixes are welcome.
|
||||
|
||||
@@ -15,7 +15,7 @@ The most fundamental pieces of any Lean program are functions organized into nam
|
||||
[Functions](./functions.md) perform work on inputs to produce outputs,
|
||||
and they are organized under [namespaces](./namespaces.md),
|
||||
which are the primary way you group things in Lean.
|
||||
They are defined using the [`def`](./definitions.md) command,
|
||||
They are defined using the `def` command,
|
||||
which give the function a name and define its arguments.
|
||||
|
||||
```lean
|
||||
|
||||
@@ -37,6 +37,6 @@ Lean has numerous features, including:
|
||||
- [Extensible syntax](./syntax.md)
|
||||
- Hygienic macros
|
||||
- [Dependent types](https://lean-lang.org/theorem_proving_in_lean4/dependent_type_theory.html)
|
||||
- [Metaprogramming](./metaprogramming.md)
|
||||
- [Metaprogramming](./macro_overview.md)
|
||||
- Multithreading
|
||||
- Verification: you can prove properties of your functions using Lean itself
|
||||
|
||||
@@ -18,6 +18,14 @@ if (LEAN_SPECIAL_VERSION_DESC)
|
||||
string(APPEND LEAN_VERSION_STRING "-${LEAN_SPECIAL_VERSION_DESC}")
|
||||
endif()
|
||||
|
||||
set(LEAN_PLATFORM_TARGET "" CACHE STRING "LLVM triple of the target platform")
|
||||
if (NOT LEAN_PLATFORM_TARGET)
|
||||
# this may fail when the compiler is not clang, but this should only happen in local builds where
|
||||
# the value of the variable is not of immediate relevance
|
||||
execute_process(COMMAND ${CMAKE_C_COMPILER} --print-target-triple
|
||||
OUTPUT_VARIABLE LEAN_PLATFORM_TARGET OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
endif()
|
||||
|
||||
set(LEAN_EXTRA_LINKER_FLAGS "" CACHE STRING "Additional flags used by the linker")
|
||||
set(LEAN_EXTRA_CXX_FLAGS "" CACHE STRING "Additional flags used by the C++ compiler")
|
||||
set(LEAN_TEST_VARS "LEAN_CC=${CMAKE_C_COMPILER}" CACHE STRING "Additional environment variables used when running tests")
|
||||
|
||||
@@ -23,4 +23,5 @@ import Init.NotationExtra
|
||||
import Init.SimpLemmas
|
||||
import Init.Hints
|
||||
import Init.Conv
|
||||
import Init.Simproc
|
||||
import Init.SizeOfLemmas
|
||||
|
||||
@@ -411,9 +411,10 @@ set_option linter.unusedVariables.funArgs false in
|
||||
be available and then calls `f` on the result.
|
||||
|
||||
`prio`, if provided, is the priority of the task.
|
||||
If `sync` is set to true, `f` is executed on the current thread if `x` has already finished.
|
||||
-/
|
||||
@[noinline, extern "lean_task_map"]
|
||||
protected def map {α : Type u} {β : Type v} (f : α → β) (x : Task α) (prio := Priority.default) : Task β :=
|
||||
protected def map (f : α → β) (x : Task α) (prio := Priority.default) (sync := false) : Task β :=
|
||||
⟨f x.get⟩
|
||||
|
||||
set_option linter.unusedVariables.funArgs false in
|
||||
@@ -424,9 +425,11 @@ for the value of `x` to be available and then calls `f` on the result,
|
||||
resulting in a new task which is then run for a result.
|
||||
|
||||
`prio`, if provided, is the priority of the task.
|
||||
If `sync` is set to true, `f` is executed on the current thread if `x` has already finished.
|
||||
-/
|
||||
@[noinline, extern "lean_task_bind"]
|
||||
protected def bind {α : Type u} {β : Type v} (x : Task α) (f : α → Task β) (prio := Priority.default) : Task β :=
|
||||
protected def bind (x : Task α) (f : α → Task β) (prio := Priority.default) (sync := false) :
|
||||
Task β :=
|
||||
⟨(f x.get).get⟩
|
||||
|
||||
end Task
|
||||
@@ -1600,6 +1603,98 @@ instance : Subsingleton (Squash α) where
|
||||
apply Quot.sound
|
||||
trivial
|
||||
|
||||
/-! # List primitives -/
|
||||
|
||||
/-- Auxiliary for `List.reverse`. `List.reverseAux l r = l.reverse ++ r`, but it is defined directly. -/
|
||||
def List.reverseAux : List α → List α → List α
|
||||
| nil, r => r
|
||||
| a :: l, r => reverseAux l (a :: r)
|
||||
|
||||
/--
|
||||
`O(|as|)`. Reverse of a list:
|
||||
* `[1, 2, 3, 4].reverse = [4, 3, 2, 1]`
|
||||
|
||||
Note that because of the "functional but in place" optimization implemented by Lean's compiler,
|
||||
this function works without any allocations provided that the input list is unshared:
|
||||
it simply walks the linked list and reverses all the node pointers.
|
||||
-/
|
||||
def List.reverse (as : List α) : List α := reverseAux as nil
|
||||
|
||||
theorem List.reverseAux_reverseAux (as bs cs : List α) :
|
||||
reverseAux (reverseAux as bs) cs = reverseAux bs (reverseAux (reverseAux as nil) cs) := by
|
||||
induction as generalizing bs with
|
||||
| nil => rfl
|
||||
| cons a as ih =>
|
||||
rw [reverseAux, reverseAux, ih (a :: bs),
|
||||
reverseAux, ih (a :: nil),
|
||||
reverseAux, reverseAux]
|
||||
|
||||
/--
|
||||
`O(|xs|)`: append two lists. `[1, 2, 3] ++ [4, 5] = [1, 2, 3, 4, 5]`.
|
||||
It takes time proportional to the first list.
|
||||
-/
|
||||
protected def List.append : (xs ys : List α) → List α
|
||||
| nil, bs => bs
|
||||
| a ::as, bs => a :: List.append as bs
|
||||
|
||||
/-- Tail-recursive version of `List.append`. -/
|
||||
def List.appendTR (as bs : List α) : List α := reverseAux as.reverse bs
|
||||
|
||||
@[csimp] theorem List.append_eq_appendTR : @List.append = @appendTR := by
|
||||
apply funext; intro α; apply funext; intro as; apply funext; intro bs
|
||||
induction as with
|
||||
| nil => rfl
|
||||
| cons a as ih =>
|
||||
rw [appendTR, reverse, reverseAux, reverseAux_reverseAux]
|
||||
exact congrArg (cons a) ih
|
||||
|
||||
/-- Convert a `List α` into an `Array α`. This is O(n) in the length of the list. -/
|
||||
@[inline, match_pattern]
|
||||
abbrev List.toArray := @Array.mk
|
||||
|
||||
/-- Auxiliary definition for `toArray`. -/
|
||||
@[inline_if_reduce]
|
||||
def List.toArrayAux : List α → Array α → Array α
|
||||
| nil, r => r
|
||||
| a :: as, r => toArrayAux as (r.push a)
|
||||
|
||||
/-- A non-tail-recursive version of `List.length`, used for `List.toArray`. -/
|
||||
@[inline_if_reduce]
|
||||
def List.redLength : List α → Nat
|
||||
| nil => 0
|
||||
| _ :: as => as.redLength.succ
|
||||
|
||||
/-- Convert a `List α` into an `Array α`. This is O(n) in the length of the list. -/
|
||||
-- This function is exported to C, where it is called by `Array.mk`
|
||||
-- (the constructor) to implement this functionality.
|
||||
@[inline, export lean_list_to_array]
|
||||
def List.toArrayTR (as : List α) : Array α := as.toArrayAux (Array.mkEmpty as.redLength)
|
||||
|
||||
theorem List.toArrayAux_eq_mk (l r : List α) : toArrayAux l ⟨r⟩ = { data := List.append r l } := by
|
||||
induction l generalizing r with
|
||||
| nil =>
|
||||
rw [toArrayAux]
|
||||
apply congrArg Array.mk
|
||||
induction r with
|
||||
| nil => rw [List.append]
|
||||
| cons b r ind => rw [List.append, ←ind]
|
||||
| cons a l ind =>
|
||||
rw [toArrayAux, Array.push, ind]
|
||||
clear ind
|
||||
apply congrArg Array.mk
|
||||
induction r generalizing l with
|
||||
| nil => rfl
|
||||
| cons b r ind =>
|
||||
rw [concat, List.append, List.append]
|
||||
exact congrArg (cons b) (ind l)
|
||||
|
||||
@[csimp] theorem List.toArray_eq_mk : @toArray = @toArrayTR := by
|
||||
apply funext ; intro α
|
||||
apply funext ; intro l
|
||||
unfold toArrayTR
|
||||
unfold Array.mkEmpty
|
||||
rw [toArrayAux_eq_mk, List.append]
|
||||
|
||||
/-! # Relations -/
|
||||
|
||||
/--
|
||||
@@ -1680,40 +1775,92 @@ So, you are mainly losing the capability of type checking your development using
|
||||
-/
|
||||
axiom ofReduceNat (a b : Nat) (h : reduceNat a = b) : a = b
|
||||
|
||||
end Lean
|
||||
|
||||
namespace Std
|
||||
variable {α : Sort u}
|
||||
|
||||
/--
|
||||
`IsAssociative op` says that `op` is an associative operation,
|
||||
i.e. `(a ∘ b) ∘ c = a ∘ (b ∘ c)`. It is used by the `ac_rfl` tactic.
|
||||
`Associative op` indicates `op` is an associative operation,
|
||||
i.e. `(a ∘ b) ∘ c = a ∘ (b ∘ c)`.
|
||||
-/
|
||||
class IsAssociative {α : Sort u} (op : α → α → α) where
|
||||
class Associative (op : α → α → α) : Prop where
|
||||
/-- An associative operation satisfies `(a ∘ b) ∘ c = a ∘ (b ∘ c)`. -/
|
||||
assoc : (a b c : α) → op (op a b) c = op a (op b c)
|
||||
|
||||
/--
|
||||
`IsCommutative op` says that `op` is a commutative operation,
|
||||
i.e. `a ∘ b = b ∘ a`. It is used by the `ac_rfl` tactic.
|
||||
`Commutative op` says that `op` is a commutative operation,
|
||||
i.e. `a ∘ b = b ∘ a`.
|
||||
-/
|
||||
class IsCommutative {α : Sort u} (op : α → α → α) where
|
||||
class Commutative (op : α → α → α) : Prop where
|
||||
/-- A commutative operation satisfies `a ∘ b = b ∘ a`. -/
|
||||
comm : (a b : α) → op a b = op b a
|
||||
|
||||
/--
|
||||
`IsIdempotent op` says that `op` is an idempotent operation,
|
||||
i.e. `a ∘ a = a`. It is used by the `ac_rfl` tactic
|
||||
(which also simplifies up to idempotence when available).
|
||||
`IdempotentOp op` indicates `op` is an idempotent binary operation.
|
||||
i.e. `a ∘ a = a`.
|
||||
-/
|
||||
class IsIdempotent {α : Sort u} (op : α → α → α) where
|
||||
class IdempotentOp (op : α → α → α) : Prop where
|
||||
/-- An idempotent operation satisfies `a ∘ a = a`. -/
|
||||
idempotent : (x : α) → op x x = x
|
||||
|
||||
/--
|
||||
`IsNeutral op e` says that `e` is a neutral operation for `op`,
|
||||
i.e. `a ∘ e = a = e ∘ a`. It is used by the `ac_rfl` tactic
|
||||
(which also simplifies neutral elements when available).
|
||||
-/
|
||||
class IsNeutral {α : Sort u} (op : α → α → α) (neutral : α) where
|
||||
/-- A neutral element can be cancelled on the left: `e ∘ a = a`. -/
|
||||
left_neutral : (a : α) → op neutral a = a
|
||||
/-- A neutral element can be cancelled on the right: `a ∘ e = a`. -/
|
||||
right_neutral : (a : α) → op a neutral = a
|
||||
`LeftIdentify op o` indicates `o` is a left identity of `op`.
|
||||
|
||||
end Lean
|
||||
This class does not require a proof that `o` is an identity, and
|
||||
is used primarily for infering the identity using class resoluton.
|
||||
-/
|
||||
class LeftIdentity (op : α → β → β) (o : outParam α) : Prop
|
||||
|
||||
/--
|
||||
`LawfulLeftIdentify op o` indicates `o` is a verified left identity of
|
||||
`op`.
|
||||
-/
|
||||
class LawfulLeftIdentity (op : α → β → β) (o : outParam α) extends LeftIdentity op o : Prop where
|
||||
/-- Left identity `o` is an identity. -/
|
||||
left_id : ∀ a, op o a = a
|
||||
|
||||
/--
|
||||
`RightIdentify op o` indicates `o` is a right identity `o` of `op`.
|
||||
|
||||
This class does not require a proof that `o` is an identity, and is used
|
||||
primarily for infering the identity using class resoluton.
|
||||
-/
|
||||
class RightIdentity (op : α → β → α) (o : outParam β) : Prop
|
||||
|
||||
/--
|
||||
`LawfulRightIdentify op o` indicates `o` is a verified right identity of
|
||||
`op`.
|
||||
-/
|
||||
class LawfulRightIdentity (op : α → β → α) (o : outParam β) extends RightIdentity op o : Prop where
|
||||
/-- Right identity `o` is an identity. -/
|
||||
right_id : ∀ a, op a o = a
|
||||
|
||||
/--
|
||||
`Identity op o` indicates `o` is a left and right identity of `op`.
|
||||
|
||||
This class does not require a proof that `o` is an identity, and is used
|
||||
primarily for infering the identity using class resoluton.
|
||||
-/
|
||||
class Identity (op : α → α → α) (o : outParam α) extends LeftIdentity op o, RightIdentity op o : Prop
|
||||
|
||||
/--
|
||||
`LawfulIdentity op o` indicates `o` is a verified left and right
|
||||
identity of `op`.
|
||||
-/
|
||||
class LawfulIdentity (op : α → α → α) (o : outParam α) extends Identity op o, LawfulLeftIdentity op o, LawfulRightIdentity op o : Prop
|
||||
|
||||
/--
|
||||
`LawfulCommIdentity` can simplify defining instances of `LawfulIdentity`
|
||||
on commutative functions by requiring only a left or right identity
|
||||
proof.
|
||||
|
||||
This class is intended for simplifying defining instances of
|
||||
`LawfulIdentity` and functions needed commutative operations with
|
||||
identity should just add a `LawfulIdentity` constraint.
|
||||
-/
|
||||
class LawfulCommIdentity (op : α → α → α) (o : outParam α) [hc : Commutative op] extends LawfulIdentity op o : Prop where
|
||||
left_id a := Eq.trans (hc.comm o a) (right_id a)
|
||||
right_id a := Eq.trans (hc.comm a o) (left_id a)
|
||||
|
||||
end Std
|
||||
|
||||
@@ -14,15 +14,17 @@ inductive Expr
|
||||
| op (lhs rhs : Expr)
|
||||
deriving Inhabited, Repr, BEq
|
||||
|
||||
open Std
|
||||
|
||||
structure Variable {α : Sort u} (op : α → α → α) : Type u where
|
||||
value : α
|
||||
neutral : Option $ IsNeutral op value
|
||||
neutral : Option $ PLift (LawfulIdentity op value)
|
||||
|
||||
structure Context (α : Sort u) where
|
||||
op : α → α → α
|
||||
assoc : IsAssociative op
|
||||
comm : Option $ IsCommutative op
|
||||
idem : Option $ IsIdempotent op
|
||||
assoc : Associative op
|
||||
comm : Option $ PLift $ Commutative op
|
||||
idem : Option $ PLift $ IdempotentOp op
|
||||
vars : List (Variable op)
|
||||
arbitrary : α
|
||||
|
||||
@@ -128,7 +130,14 @@ theorem Context.mergeIdem_head2 (h : x ≠ y) : mergeIdem (x :: y :: ys) = x ::
|
||||
simp [mergeIdem, mergeIdem.loop, h]
|
||||
|
||||
theorem Context.evalList_mergeIdem (ctx : Context α) (h : ContextInformation.isIdem ctx) (e : List Nat) : evalList α ctx (mergeIdem e) = evalList α ctx e := by
|
||||
have h : IsIdempotent ctx.op := by simp [ContextInformation.isIdem, Option.isSome] at h; cases h₂ : ctx.idem <;> simp [h₂] at h; assumption
|
||||
have h : IdempotentOp ctx.op := by
|
||||
simp [ContextInformation.isIdem, Option.isSome] at h;
|
||||
match h₂ : ctx.idem with
|
||||
| none =>
|
||||
simp [h₂] at h
|
||||
| some val =>
|
||||
simp [h₂] at h
|
||||
exact val.down
|
||||
induction e using List.two_step_induction with
|
||||
| empty => rfl
|
||||
| single => rfl
|
||||
@@ -169,7 +178,7 @@ theorem Context.sort_loop_nonEmpty (xs : List Nat) (h : xs ≠ []) : sort.loop x
|
||||
|
||||
theorem Context.evalList_insert
|
||||
(ctx : Context α)
|
||||
(h : IsCommutative ctx.op)
|
||||
(h : Commutative ctx.op)
|
||||
(x : Nat)
|
||||
(xs : List Nat)
|
||||
: evalList α ctx (insert x xs) = evalList α ctx (x::xs) := by
|
||||
@@ -190,7 +199,7 @@ theorem Context.evalList_insert
|
||||
|
||||
theorem Context.evalList_sort_congr
|
||||
(ctx : Context α)
|
||||
(h : IsCommutative ctx.op)
|
||||
(h : Commutative ctx.op)
|
||||
(h₂ : evalList α ctx a = evalList α ctx b)
|
||||
(h₃ : a ≠ [])
|
||||
(h₄ : b ≠ [])
|
||||
@@ -209,7 +218,7 @@ theorem Context.evalList_sort_congr
|
||||
|
||||
theorem Context.evalList_sort_loop_swap
|
||||
(ctx : Context α)
|
||||
(h : IsCommutative ctx.op)
|
||||
(h : Commutative ctx.op)
|
||||
(xs ys : List Nat)
|
||||
: evalList α ctx (sort.loop xs (y::ys)) = evalList α ctx (sort.loop (y::xs) ys) := by
|
||||
induction ys generalizing y xs with
|
||||
@@ -224,7 +233,7 @@ theorem Context.evalList_sort_loop_swap
|
||||
|
||||
theorem Context.evalList_sort_cons
|
||||
(ctx : Context α)
|
||||
(h : IsCommutative ctx.op)
|
||||
(h : Commutative ctx.op)
|
||||
(x : Nat)
|
||||
(xs : List Nat)
|
||||
: evalList α ctx (sort (x :: xs)) = evalList α ctx (x :: sort xs) := by
|
||||
@@ -247,7 +256,14 @@ theorem Context.evalList_sort_cons
|
||||
all_goals simp [insert_nonEmpty]
|
||||
|
||||
theorem Context.evalList_sort (ctx : Context α) (h : ContextInformation.isComm ctx) (e : List Nat) : evalList α ctx (sort e) = evalList α ctx e := by
|
||||
have h : IsCommutative ctx.op := by simp [ContextInformation.isComm, Option.isSome] at h; cases h₂ : ctx.comm <;> simp [h₂] at h; assumption
|
||||
have h : Commutative ctx.op := by
|
||||
simp [ContextInformation.isComm, Option.isSome] at h
|
||||
match h₂ : ctx.comm with
|
||||
| none =>
|
||||
simp only [h₂] at h
|
||||
| some val =>
|
||||
simp [h₂] at h
|
||||
exact val.down
|
||||
induction e using List.two_step_induction with
|
||||
| empty => rfl
|
||||
| single => rfl
|
||||
@@ -269,10 +285,12 @@ theorem Context.toList_nonEmpty (e : Expr) : e.toList ≠ [] := by
|
||||
theorem Context.unwrap_isNeutral
|
||||
{ctx : Context α}
|
||||
{x : Nat}
|
||||
: ContextInformation.isNeutral ctx x = true → IsNeutral (EvalInformation.evalOp ctx) (EvalInformation.evalVar (β := α) ctx x) := by
|
||||
: ContextInformation.isNeutral ctx x = true → LawfulIdentity (EvalInformation.evalOp ctx) (EvalInformation.evalVar (β := α) ctx x) := by
|
||||
simp [ContextInformation.isNeutral, Option.isSome, EvalInformation.evalOp, EvalInformation.evalVar]
|
||||
match (var ctx x).neutral with
|
||||
| some hn => intro; assumption
|
||||
| some hn =>
|
||||
intro
|
||||
exact hn.down
|
||||
| none => intro; contradiction
|
||||
|
||||
theorem Context.evalList_removeNeutrals (ctx : Context α) (e : List Nat) : evalList α ctx (removeNeutrals ctx e) = evalList α ctx e := by
|
||||
@@ -283,10 +301,12 @@ theorem Context.evalList_removeNeutrals (ctx : Context α) (e : List Nat) : eval
|
||||
case h_1 => rfl
|
||||
case h_2 h => split at h <;> simp_all
|
||||
| step x y ys ih =>
|
||||
cases h₁ : ContextInformation.isNeutral ctx x <;> cases h₂ : ContextInformation.isNeutral ctx y <;> cases h₃ : removeNeutrals.loop ctx ys
|
||||
cases h₁ : ContextInformation.isNeutral ctx x <;>
|
||||
cases h₂ : ContextInformation.isNeutral ctx y <;>
|
||||
cases h₃ : removeNeutrals.loop ctx ys
|
||||
<;> simp [removeNeutrals, removeNeutrals.loop, h₁, h₂, h₃, evalList, ←ih]
|
||||
<;> (try simp [unwrap_isNeutral h₂ |>.2])
|
||||
<;> (try simp [unwrap_isNeutral h₁ |>.1])
|
||||
<;> (try simp [unwrap_isNeutral h₂ |>.right_id])
|
||||
<;> (try simp [unwrap_isNeutral h₁ |>.left_id])
|
||||
|
||||
theorem Context.evalList_append
|
||||
(ctx : Context α)
|
||||
|
||||
@@ -71,6 +71,12 @@ abbrev getLit {α : Type u} {n : Nat} (a : Array α) (i : Nat) (h₁ : a.size =
|
||||
def uset (a : Array α) (i : USize) (v : α) (h : i.toNat < a.size) : Array α :=
|
||||
a.set ⟨i.toNat, h⟩ v
|
||||
|
||||
/--
|
||||
Swaps two entries in an array.
|
||||
|
||||
This will perform the update destructively provided that `a` has a reference
|
||||
count of 1 when called.
|
||||
-/
|
||||
@[extern "lean_array_fswap"]
|
||||
def swap (a : Array α) (i j : @& Fin a.size) : Array α :=
|
||||
let v₁ := a.get i
|
||||
@@ -78,12 +84,18 @@ def swap (a : Array α) (i j : @& Fin a.size) : Array α :=
|
||||
let a' := a.set i v₂
|
||||
a'.set (size_set a i v₂ ▸ j) v₁
|
||||
|
||||
/--
|
||||
Swaps two entries in an array, or panics if either index is out of bounds.
|
||||
|
||||
This will perform the update destructively provided that `a` has a reference
|
||||
count of 1 when called.
|
||||
-/
|
||||
@[extern "lean_array_swap"]
|
||||
def swap! (a : Array α) (i j : @& Nat) : Array α :=
|
||||
if h₁ : i < a.size then
|
||||
if h₂ : j < a.size then swap a ⟨i, h₁⟩ ⟨j, h₂⟩
|
||||
else panic! "index out of bounds"
|
||||
else panic! "index out of bounds"
|
||||
else a
|
||||
else a
|
||||
|
||||
@[inline] def swapAt (a : Array α) (i : Fin a.size) (v : α) : α × Array α :=
|
||||
let e := a.get i
|
||||
@@ -276,8 +288,8 @@ def mapM {α : Type u} {β : Type v} {m : Type v → Type w} [Monad m] (f : α
|
||||
map (i+1) (r.push (← f as[i]))
|
||||
else
|
||||
pure r
|
||||
termination_by as.size - i
|
||||
map 0 (mkEmpty as.size)
|
||||
termination_by map => as.size - i
|
||||
|
||||
@[inline]
|
||||
def mapIdxM {α : Type u} {β : Type v} {m : Type v → Type w} [Monad m] (as : Array α) (f : Fin as.size → α → m β) : m (Array β) :=
|
||||
@@ -348,12 +360,12 @@ def anyM {α : Type u} {m : Type → Type w} [Monad m] (p : α → m Bool) (as :
|
||||
loop (j+1)
|
||||
else
|
||||
pure false
|
||||
termination_by stop - j
|
||||
loop start
|
||||
if h : stop ≤ as.size then
|
||||
any stop h
|
||||
else
|
||||
any as.size (Nat.le_refl _)
|
||||
termination_by loop i j => stop - j
|
||||
|
||||
@[inline]
|
||||
def allM {α : Type u} {m : Type → Type w} [Monad m] (p : α → m Bool) (as : Array α) (start := 0) (stop := as.size) : m Bool :=
|
||||
@@ -523,7 +535,7 @@ def isEqvAux (a b : Array α) (hsz : a.size = b.size) (p : α → α → Bool) (
|
||||
p a[i] b[i] && isEqvAux a b hsz p (i+1)
|
||||
else
|
||||
true
|
||||
termination_by _ => a.size - i
|
||||
termination_by a.size - i
|
||||
|
||||
@[inline] def isEqv (a b : Array α) (p : α → α → Bool) : Bool :=
|
||||
if h : a.size = b.size then
|
||||
@@ -627,7 +639,7 @@ def indexOfAux [BEq α] (a : Array α) (v : α) (i : Nat) : Option (Fin a.size)
|
||||
if a.get idx == v then some idx
|
||||
else indexOfAux a v (i+1)
|
||||
else none
|
||||
termination_by _ => a.size - i
|
||||
termination_by a.size - i
|
||||
|
||||
def indexOf? [BEq α] (a : Array α) (v : α) : Option (Fin a.size) :=
|
||||
indexOfAux a v 0
|
||||
@@ -659,7 +671,7 @@ where
|
||||
loop as (i+1) ⟨j-1, this⟩
|
||||
else
|
||||
as
|
||||
termination_by _ => j - i
|
||||
termination_by j - i
|
||||
|
||||
def popWhile (p : α → Bool) (as : Array α) : Array α :=
|
||||
if h : as.size > 0 then
|
||||
@@ -669,7 +681,7 @@ def popWhile (p : α → Bool) (as : Array α) : Array α :=
|
||||
as
|
||||
else
|
||||
as
|
||||
termination_by popWhile as => as.size
|
||||
termination_by as.size
|
||||
|
||||
def takeWhile (p : α → Bool) (as : Array α) : Array α :=
|
||||
let rec go (i : Nat) (r : Array α) : Array α :=
|
||||
@@ -681,8 +693,8 @@ def takeWhile (p : α → Bool) (as : Array α) : Array α :=
|
||||
r
|
||||
else
|
||||
r
|
||||
termination_by as.size - i
|
||||
go 0 #[]
|
||||
termination_by go i r => as.size - i
|
||||
|
||||
def eraseIdxAux (i : Nat) (a : Array α) : Array α :=
|
||||
if h : i < a.size then
|
||||
@@ -692,7 +704,7 @@ def eraseIdxAux (i : Nat) (a : Array α) : Array α :=
|
||||
eraseIdxAux (i+1) a'
|
||||
else
|
||||
a.pop
|
||||
termination_by _ => a.size - i
|
||||
termination_by a.size - i
|
||||
|
||||
def feraseIdx (a : Array α) (i : Fin a.size) : Array α :=
|
||||
eraseIdxAux (i.val + 1) a
|
||||
@@ -707,7 +719,7 @@ def eraseIdxSzAux (a : Array α) (i : Nat) (r : Array α) (heq : r.size = a.size
|
||||
eraseIdxSzAux a (i+1) (r.swap idx idx1) ((size_swap r idx idx1).trans heq)
|
||||
else
|
||||
⟨r.pop, (size_pop r).trans (heq ▸ rfl)⟩
|
||||
termination_by _ => r.size - i
|
||||
termination_by r.size - i
|
||||
|
||||
def eraseIdx' (a : Array α) (i : Fin a.size) : { r : Array α // r.size = a.size - 1 } :=
|
||||
eraseIdxSzAux a (i.val + 1) a rfl
|
||||
@@ -726,10 +738,10 @@ def erase [BEq α] (as : Array α) (a : α) : Array α :=
|
||||
loop as ⟨j', by rw [size_swap]; exact j'.2⟩
|
||||
else
|
||||
as
|
||||
termination_by j.1
|
||||
let j := as.size
|
||||
let as := as.push a
|
||||
loop as ⟨j, size_push .. ▸ j.lt_succ_self⟩
|
||||
termination_by loop j => j.1
|
||||
|
||||
/-- Insert element `a` at position `i`. Panics if `i` is not `i ≤ as.size`. -/
|
||||
def insertAt! (as : Array α) (i : Nat) (a : α) : Array α :=
|
||||
@@ -779,7 +791,7 @@ def isPrefixOfAux [BEq α] (as bs : Array α) (hle : as.size ≤ bs.size) (i : N
|
||||
false
|
||||
else
|
||||
true
|
||||
termination_by _ => as.size - i
|
||||
termination_by as.size - i
|
||||
|
||||
/-- Return true iff `as` is a prefix of `bs`.
|
||||
That is, `bs = as ++ t` for some `t : List α`.-/
|
||||
@@ -800,7 +812,7 @@ private def allDiffAux [BEq α] (as : Array α) (i : Nat) : Bool :=
|
||||
allDiffAuxAux as as[i] i h && allDiffAux as (i+1)
|
||||
else
|
||||
true
|
||||
termination_by _ => as.size - i
|
||||
termination_by as.size - i
|
||||
|
||||
def allDiff [BEq α] (as : Array α) : Bool :=
|
||||
allDiffAux as 0
|
||||
@@ -815,7 +827,7 @@ def allDiff [BEq α] (as : Array α) : Bool :=
|
||||
cs
|
||||
else
|
||||
cs
|
||||
termination_by _ => as.size - i
|
||||
termination_by as.size - i
|
||||
|
||||
@[inline] def zipWith (as : Array α) (bs : Array β) (f : α → β → γ) : Array γ :=
|
||||
zipWithAux f as bs 0 #[]
|
||||
|
||||
@@ -32,11 +32,6 @@ private theorem List.of_toArrayAux_eq_toArrayAux {as bs : List α} {cs ds : Arra
|
||||
have := Array.of_push_eq_push ih₂
|
||||
simp [this]
|
||||
|
||||
@[simp] theorem List.toArray_eq_toArray_eq (as bs : List α) : (as.toArray = bs.toArray) = (as = bs) := by
|
||||
apply propext; apply Iff.intro
|
||||
· intro h; simp [toArray] at h; have := of_toArrayAux_eq_toArrayAux h rfl; exact this.1
|
||||
· intro h; rw [h]
|
||||
|
||||
def Array.mapM' [Monad m] (f : α → m β) (as : Array α) : m { bs : Array β // bs.size = as.size } :=
|
||||
go 0 ⟨mkEmpty as.size, rfl⟩ (by simp_arith)
|
||||
where
|
||||
@@ -47,7 +42,7 @@ where
|
||||
have hlt : i < as.size := Nat.lt_of_le_of_ne hle h
|
||||
let b ← f as[i]
|
||||
go (i+1) ⟨acc.val.push b, by simp [acc.property]⟩ hlt
|
||||
termination_by go i _ _ => as.size - i
|
||||
termination_by as.size - i
|
||||
|
||||
@[inline] private unsafe def mapMonoMImp [Monad m] (as : Array α) (f : α → m α) : m (Array α) :=
|
||||
go 0 as
|
||||
|
||||
@@ -20,7 +20,7 @@ theorem eq_of_isEqvAux [DecidableEq α] (a b : Array α) (hsz : a.size = b.size)
|
||||
· have heq : i = a.size := Nat.le_antisymm hi (Nat.ge_of_not_lt h)
|
||||
subst heq
|
||||
exact absurd (Nat.lt_of_lt_of_le high low) (Nat.lt_irrefl j)
|
||||
termination_by _ => a.size - i
|
||||
termination_by a.size - i
|
||||
|
||||
theorem eq_of_isEqv [DecidableEq α] (a b : Array α) : Array.isEqv a b (fun x y => x = y) → a = b := by
|
||||
simp [Array.isEqv]
|
||||
@@ -36,7 +36,7 @@ theorem isEqvAux_self [DecidableEq α] (a : Array α) (i : Nat) : Array.isEqvAux
|
||||
split
|
||||
case inl h => simp [h, isEqvAux_self a (i+1)]
|
||||
case inr h => simp [h]
|
||||
termination_by _ => a.size - i
|
||||
termination_by a.size - i
|
||||
|
||||
theorem isEqv_self [DecidableEq α] (a : Array α) : Array.isEqv a a (fun x y => x = y) = true := by
|
||||
simp [isEqv, isEqvAux_self]
|
||||
|
||||
@@ -26,8 +26,8 @@ def qpartition (as : Array α) (lt : α → α → Bool) (lo hi : Nat) : Nat ×
|
||||
else
|
||||
let as := as.swap! i hi
|
||||
(i, as)
|
||||
termination_by hi - j
|
||||
loop as lo lo
|
||||
termination_by _ => hi - j
|
||||
|
||||
@[inline] partial def qsort (as : Array α) (lt : α → α → Bool) (low := 0) (high := as.size - 1) : Array α :=
|
||||
let rec @[specialize] sort (as : Array α) (low high : Nat) :=
|
||||
|
||||
@@ -100,7 +100,7 @@ instance : ShiftLeft (Fin n) where
|
||||
instance : ShiftRight (Fin n) where
|
||||
shiftRight := Fin.shiftRight
|
||||
|
||||
instance : OfNat (Fin (no_index (n+1))) i where
|
||||
instance instOfNat : OfNat (Fin (no_index (n+1))) i where
|
||||
ofNat := Fin.ofNat i
|
||||
|
||||
instance : Inhabited (Fin (no_index (n+1))) where
|
||||
|
||||
@@ -26,6 +26,8 @@ opaque floatSpec : FloatSpec := {
|
||||
decLe := fun _ _ => inferInstanceAs (Decidable True)
|
||||
}
|
||||
|
||||
/-- Native floating point type, corresponding to the IEEE 754 *binary64* format
|
||||
(`double` in C or `f64` in Rust). -/
|
||||
structure Float where
|
||||
val : floatSpec.float
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ attribute [extern "lean_int_neg_succ_of_nat"] Int.negSucc
|
||||
|
||||
instance : Coe Nat Int := ⟨Int.ofNat⟩
|
||||
|
||||
instance : OfNat Int n where
|
||||
instance instOfNat : OfNat Int n where
|
||||
ofNat := Int.ofNat n
|
||||
|
||||
namespace Int
|
||||
|
||||
@@ -77,55 +77,14 @@ theorem length_add_eq_lengthTRAux (as : List α) (n : Nat) : as.length + n = as.
|
||||
@[simp] theorem length_nil : length ([] : List α) = 0 :=
|
||||
rfl
|
||||
|
||||
/-- Auxiliary for `List.reverse`. `List.reverseAux l r = l.reverse ++ r`, but it is defined directly. -/
|
||||
def reverseAux : List α → List α → List α
|
||||
| [], r => r
|
||||
| a::l, r => reverseAux l (a::r)
|
||||
|
||||
/--
|
||||
`O(|as|)`. Reverse of a list:
|
||||
* `[1, 2, 3, 4].reverse = [4, 3, 2, 1]`
|
||||
|
||||
Note that because of the "functional but in place" optimization implemented by Lean's compiler,
|
||||
this function works without any allocations provided that the input list is unshared:
|
||||
it simply walks the linked list and reverses all the node pointers.
|
||||
-/
|
||||
def reverse (as : List α) : List α :=
|
||||
reverseAux as []
|
||||
|
||||
theorem reverseAux_reverseAux_nil (as bs : List α) : reverseAux (reverseAux as bs) [] = reverseAux bs as := by
|
||||
induction as generalizing bs with
|
||||
| nil => rfl
|
||||
| cons a as ih => simp [reverseAux, ih]
|
||||
|
||||
theorem reverseAux_reverseAux (as bs cs : List α) : reverseAux (reverseAux as bs) cs = reverseAux bs (reverseAux (reverseAux as []) cs) := by
|
||||
induction as generalizing bs cs with
|
||||
| nil => rfl
|
||||
| cons a as ih => simp [reverseAux, ih (a::bs), ih [a]]
|
||||
|
||||
@[simp] theorem reverse_reverse (as : List α) : as.reverse.reverse = as := by
|
||||
simp [reverse]; rw [reverseAux_reverseAux_nil]; rfl
|
||||
|
||||
/--
|
||||
`O(|xs|)`: append two lists. `[1, 2, 3] ++ [4, 5] = [1, 2, 3, 4, 5]`.
|
||||
It takes time proportional to the first list.
|
||||
-/
|
||||
protected def append : (xs ys : List α) → List α
|
||||
| [], bs => bs
|
||||
| a::as, bs => a :: List.append as bs
|
||||
|
||||
/-- Tail-recursive version of `List.append`. -/
|
||||
def appendTR (as bs : List α) : List α :=
|
||||
reverseAux as.reverse bs
|
||||
|
||||
@[csimp] theorem append_eq_appendTR : @List.append = @appendTR := by
|
||||
apply funext; intro α; apply funext; intro as; apply funext; intro bs
|
||||
simp [appendTR, reverse]
|
||||
induction as with
|
||||
| nil => rfl
|
||||
| cons a as ih =>
|
||||
simp [reverseAux, List.append, ih, reverseAux_reverseAux]
|
||||
|
||||
instance : Append (List α) := ⟨List.append⟩
|
||||
|
||||
@[simp] theorem nil_append (as : List α) : [] ++ as = as := rfl
|
||||
|
||||
@@ -8,14 +8,14 @@ import Init.Data.Nat.Div
|
||||
|
||||
namespace Nat
|
||||
|
||||
private def gcdF (x : Nat) : (∀ x₁, x₁ < x → Nat → Nat) → Nat → Nat :=
|
||||
match x with
|
||||
| 0 => fun _ y => y
|
||||
| succ x => fun f y => f (y % succ x) (mod_lt _ (zero_lt_succ _)) (succ x)
|
||||
|
||||
@[extern "lean_nat_gcd"]
|
||||
def gcd (a b : @& Nat) : Nat :=
|
||||
WellFounded.fix (measure id).wf gcdF a b
|
||||
def gcd (m n : @& Nat) : Nat :=
|
||||
if m = 0 then
|
||||
n
|
||||
else
|
||||
gcd (n % m) m
|
||||
termination_by m
|
||||
decreasing_by simp_wf; apply mod_lt _ (zero_lt_of_ne_zero _); assumption
|
||||
|
||||
@[simp] theorem gcd_zero_left (y : Nat) : gcd 0 y = y :=
|
||||
rfl
|
||||
|
||||
@@ -21,8 +21,8 @@ where
|
||||
go (power * 2) (Nat.mul_pos h (by decide))
|
||||
else
|
||||
power
|
||||
termination_by go p h => n - p
|
||||
decreasing_by simp_wf; apply nextPowerOfTwo_dec <;> assumption
|
||||
termination_by n - power
|
||||
decreasing_by simp_wf; apply nextPowerOfTwo_dec <;> assumption
|
||||
|
||||
def isPowerOfTwo (n : Nat) := ∃ k, n = 2 ^ k
|
||||
|
||||
@@ -48,7 +48,7 @@ where
|
||||
split
|
||||
. exact isPowerOfTwo_go (power*2) (Nat.mul_pos h₁ (by decide)) (Nat.mul2_isPowerOfTwo_of_isPowerOfTwo h₂)
|
||||
. assumption
|
||||
termination_by isPowerOfTwo_go p _ _ => n - p
|
||||
decreasing_by simp_wf; apply nextPowerOfTwo_dec <;> assumption
|
||||
termination_by n - power
|
||||
decreasing_by simp_wf; apply nextPowerOfTwo_dec <;> assumption
|
||||
|
||||
end Nat
|
||||
|
||||
@@ -6,7 +6,7 @@ Authors: Leonardo de Moura
|
||||
prelude
|
||||
import Init.Meta
|
||||
import Init.Data.Float
|
||||
import Init.Data.Nat
|
||||
import Init.Data.Nat.Log2
|
||||
|
||||
/-- For decimal and scientific numbers (e.g., `1.23`, `3.12e10`).
|
||||
Examples:
|
||||
|
||||
@@ -76,10 +76,12 @@ macro_rules
|
||||
end Range
|
||||
end Std
|
||||
|
||||
theorem Membership.mem.upper {i : Nat} {r : Std.Range} (h : i ∈ r) : i < r.stop := by
|
||||
simp [Membership.mem] at h
|
||||
exact h.2
|
||||
theorem Membership.mem.upper {i : Nat} {r : Std.Range} (h : i ∈ r) : i < r.stop := h.2
|
||||
|
||||
theorem Membership.mem.lower {i : Nat} {r : Std.Range} (h : i ∈ r) : r.start ≤ i := by
|
||||
simp [Membership.mem] at h
|
||||
exact h.1
|
||||
theorem Membership.mem.lower {i : Nat} {r : Std.Range} (h : i ∈ r) : r.start ≤ i := h.1
|
||||
|
||||
theorem Membership.get_elem_helper {i n : Nat} {r : Std.Range} (h₁ : i ∈ r) (h₂ : r.stop = n) :
|
||||
i < n := h₂ ▸ h₁.2
|
||||
|
||||
macro_rules
|
||||
| `(tactic| get_elem_tactic_trivial) => `(tactic| apply Membership.get_elem_helper; assumption; rfl)
|
||||
|
||||
@@ -159,7 +159,7 @@ def posOfAux (s : String) (c : Char) (stopPos : Pos) (pos : Pos) : Pos :=
|
||||
have := Nat.sub_lt_sub_left h (lt_next s pos)
|
||||
posOfAux s c stopPos (s.next pos)
|
||||
else pos
|
||||
termination_by _ => stopPos.1 - pos.1
|
||||
termination_by stopPos.1 - pos.1
|
||||
|
||||
@[inline] def posOf (s : String) (c : Char) : Pos :=
|
||||
posOfAux s c s.endPos 0
|
||||
@@ -171,7 +171,7 @@ def revPosOfAux (s : String) (c : Char) (pos : Pos) : Option Pos :=
|
||||
let pos := s.prev pos
|
||||
if s.get pos == c then some pos
|
||||
else revPosOfAux s c pos
|
||||
termination_by _ => pos.1
|
||||
termination_by pos.1
|
||||
|
||||
def revPosOf (s : String) (c : Char) : Option Pos :=
|
||||
revPosOfAux s c s.endPos
|
||||
@@ -183,7 +183,7 @@ def findAux (s : String) (p : Char → Bool) (stopPos : Pos) (pos : Pos) : Pos :
|
||||
have := Nat.sub_lt_sub_left h (lt_next s pos)
|
||||
findAux s p stopPos (s.next pos)
|
||||
else pos
|
||||
termination_by _ => stopPos.1 - pos.1
|
||||
termination_by stopPos.1 - pos.1
|
||||
|
||||
@[inline] def find (s : String) (p : Char → Bool) : Pos :=
|
||||
findAux s p s.endPos 0
|
||||
@@ -195,7 +195,7 @@ def revFindAux (s : String) (p : Char → Bool) (pos : Pos) : Option Pos :=
|
||||
let pos := s.prev pos
|
||||
if p (s.get pos) then some pos
|
||||
else revFindAux s p pos
|
||||
termination_by _ => pos.1
|
||||
termination_by pos.1
|
||||
|
||||
def revFind (s : String) (p : Char → Bool) : Option Pos :=
|
||||
revFindAux s p s.endPos
|
||||
@@ -213,8 +213,8 @@ def firstDiffPos (a b : String) : Pos :=
|
||||
have := Nat.sub_lt_sub_left h (lt_next a i)
|
||||
loop (a.next i)
|
||||
else i
|
||||
termination_by stopPos.1 - i.1
|
||||
loop 0
|
||||
termination_by loop => stopPos.1 - i.1
|
||||
|
||||
@[extern "lean_string_utf8_extract"]
|
||||
def extract : (@& String) → (@& Pos) → (@& Pos) → String
|
||||
@@ -240,7 +240,7 @@ where
|
||||
splitAux s p i' i' (s.extract b i :: r)
|
||||
else
|
||||
splitAux s p b (s.next i) r
|
||||
termination_by _ => s.endPos.1 - i.1
|
||||
termination_by s.endPos.1 - i.1
|
||||
|
||||
@[specialize] def split (s : String) (p : Char → Bool) : List String :=
|
||||
splitAux s p 0 0 []
|
||||
@@ -260,7 +260,7 @@ def splitOnAux (s sep : String) (b : Pos) (i : Pos) (j : Pos) (r : List String)
|
||||
splitOnAux s sep b i j r
|
||||
else
|
||||
splitOnAux s sep b (s.next i) 0 r
|
||||
termination_by _ => s.endPos.1 - i.1
|
||||
termination_by s.endPos.1 - i.1
|
||||
|
||||
def splitOn (s : String) (sep : String := " ") : List String :=
|
||||
if sep == "" then [s] else splitOnAux s sep 0 0 0 []
|
||||
@@ -369,7 +369,7 @@ def offsetOfPosAux (s : String) (pos : Pos) (i : Pos) (offset : Nat) : Nat :=
|
||||
else
|
||||
have := Nat.sub_lt_sub_left (Nat.gt_of_not_le (mt decide_eq_true h)) (lt_next s _)
|
||||
offsetOfPosAux s pos (s.next i) (offset+1)
|
||||
termination_by _ => s.endPos.1 - i.1
|
||||
termination_by s.endPos.1 - i.1
|
||||
|
||||
def offsetOfPos (s : String) (pos : Pos) : Nat :=
|
||||
offsetOfPosAux s pos 0 0
|
||||
@@ -379,7 +379,7 @@ def offsetOfPos (s : String) (pos : Pos) : Nat :=
|
||||
have := Nat.sub_lt_sub_left h (lt_next s i)
|
||||
foldlAux f s stopPos (s.next i) (f a (s.get i))
|
||||
else a
|
||||
termination_by _ => stopPos.1 - i.1
|
||||
termination_by stopPos.1 - i.1
|
||||
|
||||
@[inline] def foldl {α : Type u} (f : α → Char → α) (init : α) (s : String) : α :=
|
||||
foldlAux f s s.endPos 0 init
|
||||
@@ -392,7 +392,7 @@ termination_by _ => stopPos.1 - i.1
|
||||
let a := f (s.get i) a
|
||||
foldrAux f a s i begPos
|
||||
else a
|
||||
termination_by _ => i.1
|
||||
termination_by i.1
|
||||
|
||||
@[inline] def foldr {α : Type u} (f : Char → α → α) (init : α) (s : String) : α :=
|
||||
foldrAux f init s s.endPos 0
|
||||
@@ -404,7 +404,7 @@ termination_by _ => i.1
|
||||
have := Nat.sub_lt_sub_left h (lt_next s i)
|
||||
anyAux s stopPos p (s.next i)
|
||||
else false
|
||||
termination_by _ => stopPos.1 - i.1
|
||||
termination_by stopPos.1 - i.1
|
||||
|
||||
@[inline] def any (s : String) (p : Char → Bool) : Bool :=
|
||||
anyAux s s.endPos p 0
|
||||
@@ -463,7 +463,7 @@ theorem mapAux_lemma (s : String) (i : Pos) (c : Char) (h : ¬s.atEnd i) :
|
||||
have := mapAux_lemma s i c h
|
||||
let s := s.set i c
|
||||
mapAux f (s.next i) s
|
||||
termination_by _ => s.endPos.1 - i.1
|
||||
termination_by s.endPos.1 - i.1
|
||||
|
||||
@[inline] def map (f : Char → Char) (s : String) : String :=
|
||||
mapAux f 0 s
|
||||
@@ -490,7 +490,7 @@ where
|
||||
have := Nat.sub_lt_sub_left h (Nat.add_lt_add_left (one_le_csize c₁) off1.1)
|
||||
c₁ == c₂ && loop (off1 + c₁) (off2 + c₂) stop1
|
||||
else true
|
||||
termination_by loop => stop1.1 - off1.1
|
||||
termination_by stop1.1 - off1.1
|
||||
|
||||
/-- Return true iff `p` is a prefix of `s` -/
|
||||
def isPrefixOf (p : String) (s : String) : Bool :=
|
||||
@@ -512,8 +512,8 @@ def replace (s pattern replacement : String) : String :=
|
||||
else
|
||||
have := Nat.sub_lt_sub_left this (lt_next s pos)
|
||||
loop acc accStop (s.next pos)
|
||||
termination_by s.endPos.1 - pos.1
|
||||
loop "" 0 0
|
||||
termination_by loop => s.endPos.1 - pos.1
|
||||
|
||||
end String
|
||||
|
||||
@@ -612,8 +612,8 @@ def splitOn (s : Substring) (sep : String := " ") : List Substring :=
|
||||
else
|
||||
s.extract b i :: r
|
||||
r.reverse
|
||||
termination_by s.bsize - i.1
|
||||
loop 0 0 0 []
|
||||
termination_by loop => s.bsize - i.1
|
||||
|
||||
@[inline] def foldl {α : Type u} (f : α → Char → α) (init : α) (s : Substring) : α :=
|
||||
match s with
|
||||
@@ -640,7 +640,7 @@ def contains (s : Substring) (c : Char) : Bool :=
|
||||
takeWhileAux s stopPos p (s.next i)
|
||||
else i
|
||||
else i
|
||||
termination_by _ => stopPos.1 - i.1
|
||||
termination_by stopPos.1 - i.1
|
||||
|
||||
@[inline] def takeWhile : Substring → (Char → Bool) → Substring
|
||||
| ⟨s, b, e⟩, p =>
|
||||
@@ -661,7 +661,7 @@ termination_by _ => stopPos.1 - i.1
|
||||
if !p c then i
|
||||
else takeRightWhileAux s begPos p i'
|
||||
else i
|
||||
termination_by _ => i.1
|
||||
termination_by i.1
|
||||
|
||||
@[inline] def takeRightWhile : Substring → (Char → Bool) → Substring
|
||||
| ⟨s, b, e⟩, p =>
|
||||
|
||||
@@ -5,7 +5,6 @@ Authors: Leonardo de Moura
|
||||
-/
|
||||
prelude
|
||||
import Init.Data.Fin.Basic
|
||||
import Init.System.Platform
|
||||
|
||||
open Nat
|
||||
|
||||
@@ -39,7 +38,7 @@ def UInt8.shiftRight (a b : UInt8) : UInt8 := ⟨a.val >>> (modn b 8).val⟩
|
||||
def UInt8.lt (a b : UInt8) : Prop := a.val < b.val
|
||||
def UInt8.le (a b : UInt8) : Prop := a.val ≤ b.val
|
||||
|
||||
instance : OfNat UInt8 n := ⟨UInt8.ofNat n⟩
|
||||
instance UInt8.instOfNat : OfNat UInt8 n := ⟨UInt8.ofNat n⟩
|
||||
instance : Add UInt8 := ⟨UInt8.add⟩
|
||||
instance : Sub UInt8 := ⟨UInt8.sub⟩
|
||||
instance : Mul UInt8 := ⟨UInt8.mul⟩
|
||||
@@ -110,8 +109,7 @@ def UInt16.shiftRight (a b : UInt16) : UInt16 := ⟨a.val >>> (modn b 16).val⟩
|
||||
def UInt16.lt (a b : UInt16) : Prop := a.val < b.val
|
||||
def UInt16.le (a b : UInt16) : Prop := a.val ≤ b.val
|
||||
|
||||
|
||||
instance : OfNat UInt16 n := ⟨UInt16.ofNat n⟩
|
||||
instance UInt16.instOfNat : OfNat UInt16 n := ⟨UInt16.ofNat n⟩
|
||||
instance : Add UInt16 := ⟨UInt16.add⟩
|
||||
instance : Sub UInt16 := ⟨UInt16.sub⟩
|
||||
instance : Mul UInt16 := ⟨UInt16.mul⟩
|
||||
@@ -152,6 +150,14 @@ instance : Min UInt16 := minOfLe
|
||||
def UInt32.ofNat (n : @& Nat) : UInt32 := ⟨Fin.ofNat n⟩
|
||||
@[extern "lean_uint32_of_nat"]
|
||||
def UInt32.ofNat' (n : Nat) (h : n < UInt32.size) : UInt32 := ⟨⟨n, h⟩⟩
|
||||
/--
|
||||
Converts the given natural number to `UInt32`, but returns `2^32 - 1` for natural numbers `>= 2^32`.
|
||||
-/
|
||||
def UInt32.ofNatTruncate (n : Nat) : UInt32 :=
|
||||
if h : n < UInt32.size then
|
||||
UInt32.ofNat' n h
|
||||
else
|
||||
UInt32.ofNat' (UInt32.size - 1) (by decide)
|
||||
abbrev Nat.toUInt32 := UInt32.ofNat
|
||||
@[extern "lean_uint32_add"]
|
||||
def UInt32.add (a b : UInt32) : UInt32 := ⟨a.val + b.val⟩
|
||||
@@ -184,7 +190,7 @@ def UInt8.toUInt32 (a : UInt8) : UInt32 := a.toNat.toUInt32
|
||||
@[extern "lean_uint16_to_uint32"]
|
||||
def UInt16.toUInt32 (a : UInt16) : UInt32 := a.toNat.toUInt32
|
||||
|
||||
instance : OfNat UInt32 n := ⟨UInt32.ofNat n⟩
|
||||
instance UInt32.instOfNat : OfNat UInt32 n := ⟨UInt32.ofNat n⟩
|
||||
instance : Add UInt32 := ⟨UInt32.add⟩
|
||||
instance : Sub UInt32 := ⟨UInt32.sub⟩
|
||||
instance : Mul UInt32 := ⟨UInt32.mul⟩
|
||||
@@ -244,7 +250,7 @@ def UInt16.toUInt64 (a : UInt16) : UInt64 := a.toNat.toUInt64
|
||||
@[extern "lean_uint32_to_uint64"]
|
||||
def UInt32.toUInt64 (a : UInt32) : UInt64 := a.toNat.toUInt64
|
||||
|
||||
instance : OfNat UInt64 n := ⟨UInt64.ofNat n⟩
|
||||
instance UInt64.instOfNat : OfNat UInt64 n := ⟨UInt64.ofNat n⟩
|
||||
instance : Add UInt64 := ⟨UInt64.add⟩
|
||||
instance : Sub UInt64 := ⟨UInt64.sub⟩
|
||||
instance : Mul UInt64 := ⟨UInt64.mul⟩
|
||||
@@ -322,7 +328,7 @@ def USize.toUInt32 (a : USize) : UInt32 := a.toNat.toUInt32
|
||||
def USize.lt (a b : USize) : Prop := a.val < b.val
|
||||
def USize.le (a b : USize) : Prop := a.val ≤ b.val
|
||||
|
||||
instance : OfNat USize n := ⟨USize.ofNat n⟩
|
||||
instance USize.instOfNat : OfNat USize n := ⟨USize.ofNat n⟩
|
||||
instance : Add USize := ⟨USize.add⟩
|
||||
instance : Sub USize := ⟨USize.sub⟩
|
||||
instance : Mul USize := ⟨USize.mul⟩
|
||||
|
||||
@@ -1039,7 +1039,7 @@ where
|
||||
go (i+1) (args.push (quote xs[i]))
|
||||
else
|
||||
Syntax.mkCApp (Name.mkStr2 "Array" ("mkArray" ++ toString xs.size)) args
|
||||
termination_by go i _ => xs.size - i
|
||||
termination_by xs.size - i
|
||||
|
||||
instance [Quote α `term] : Quote (Array α) `term where
|
||||
quote := quoteArray
|
||||
|
||||
@@ -2713,25 +2713,6 @@ def Array.extract (as : Array α) (start stop : Nat) : Array α :=
|
||||
let sz' := Nat.sub (min stop as.size) start
|
||||
loop sz' start (mkEmpty sz')
|
||||
|
||||
/-- Auxiliary definition for `List.toArray`. -/
|
||||
@[inline_if_reduce]
|
||||
def List.toArrayAux : List α → Array α → Array α
|
||||
| nil, r => r
|
||||
| cons a as, r => toArrayAux as (r.push a)
|
||||
|
||||
/-- A non-tail-recursive version of `List.length`, used for `List.toArray`. -/
|
||||
@[inline_if_reduce]
|
||||
def List.redLength : List α → Nat
|
||||
| nil => 0
|
||||
| cons _ as => as.redLength.succ
|
||||
|
||||
/-- Convert a `List α` into an `Array α`. This is O(n) in the length of the list. -/
|
||||
-- This function is exported to C, where it is called by `Array.mk`
|
||||
-- (the constructor) to implement this functionality.
|
||||
@[inline, match_pattern, export lean_list_to_array]
|
||||
def List.toArray (as : List α) : Array α :=
|
||||
as.toArrayAux (Array.mkEmpty as.redLength)
|
||||
|
||||
/-- The typeclass which supplies the `>>=` "bind" function. See `Monad`. -/
|
||||
class Bind (m : Type u → Type v) where
|
||||
/-- If `x : m α` and `f : α → m β`, then `x >>= f : m β` represents the
|
||||
@@ -3231,7 +3212,7 @@ instance (σ : Type u) (m : Type u → Type v) [MonadStateOf σ m] : MonadState
|
||||
/--
|
||||
`modify (f : σ → σ)` applies the function `f` to the state.
|
||||
|
||||
It is equivalent to `do put (f (← get))`, but `modify f` may be preferable
|
||||
It is equivalent to `do set (f (← get))`, but `modify f` may be preferable
|
||||
because the former does not use the state linearly (without sufficient inlining).
|
||||
-/
|
||||
@[always_inline, inline]
|
||||
|
||||
@@ -10,6 +10,7 @@ import Init.Core
|
||||
set_option linter.missingDocs true -- keep it documented
|
||||
|
||||
theorem of_eq_true (h : p = True) : p := h ▸ trivial
|
||||
theorem of_eq_false (h : p = False) : ¬ p := fun hp => False.elim (h.mp hp)
|
||||
|
||||
theorem eq_true (h : p) : p = True :=
|
||||
propext ⟨fun _ => trivial, fun _ => h⟩
|
||||
@@ -84,6 +85,13 @@ theorem dite_congr {_ : Decidable b} [Decidable c]
|
||||
@[simp] theorem ite_false (a b : α) : (if False then a else b) = b := rfl
|
||||
@[simp] theorem dite_true {α : Sort u} {t : True → α} {e : ¬ True → α} : (dite True t e) = t True.intro := rfl
|
||||
@[simp] theorem dite_false {α : Sort u} {t : False → α} {e : ¬ False → α} : (dite False t e) = e not_false := rfl
|
||||
section SimprocHelperLemmas
|
||||
set_option simprocs false
|
||||
theorem ite_cond_eq_true {α : Sort u} {c : Prop} {_ : Decidable c} (a b : α) (h : c = True) : (if c then a else b) = a := by simp [h]
|
||||
theorem ite_cond_eq_false {α : Sort u} {c : Prop} {_ : Decidable c} (a b : α) (h : c = False) : (if c then a else b) = b := by simp [h]
|
||||
theorem dite_cond_eq_true {α : Sort u} {c : Prop} {_ : Decidable c} {t : c → α} {e : ¬ c → α} (h : c = True) : (dite c t e) = t (of_eq_true h) := by simp [h]
|
||||
theorem dite_cond_eq_false {α : Sort u} {c : Prop} {_ : Decidable c} {t : c → α} {e : ¬ c → α} (h : c = False) : (dite c t e) = e (of_eq_false h) := by simp [h]
|
||||
end SimprocHelperLemmas
|
||||
@[simp] theorem ite_self {α : Sort u} {c : Prop} {d : Decidable c} (a : α) : ite c a a = a := by cases d <;> rfl
|
||||
@[simp] theorem and_self (p : Prop) : (p ∧ p) = p := propext ⟨(·.1), fun h => ⟨h, h⟩⟩
|
||||
@[simp] theorem and_true (p : Prop) : (p ∧ True) = p := propext ⟨(·.1), (⟨·, trivial⟩)⟩
|
||||
|
||||
94
src/Init/Simproc.lean
Normal file
94
src/Init/Simproc.lean
Normal file
@@ -0,0 +1,94 @@
|
||||
/-
|
||||
Copyright (c) 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
prelude
|
||||
import Init.NotationExtra
|
||||
|
||||
namespace Lean.Parser
|
||||
/--
|
||||
A user-defined simplification procedure used by the `simp` tactic, and its variants.
|
||||
Here is an example.
|
||||
```lean
|
||||
simproc reduce_add (_ + _) := fun e => do
|
||||
unless (e.isAppOfArity ``HAdd.hAdd 6) do return none
|
||||
let some n ← getNatValue? (e.getArg! 4) | return none
|
||||
let some m ← getNatValue? (e.getArg! 5) | return none
|
||||
return some (.done { expr := mkNatLit (n+m) })
|
||||
```
|
||||
The `simp` tactic invokes `reduce_add` whenever it finds a term of the form `_ + _`.
|
||||
The simplification procedures are stored in an (imperfect) discrimination tree.
|
||||
The procedure should **not** assume the term `e` perfectly matches the given pattern.
|
||||
The body of a simplification procedure must have type `Simproc`, which is an alias for
|
||||
`Expr → SimpM (Option Step)`.
|
||||
You can instruct the simplifier to apply the procedure before its sub-expressions
|
||||
have been simplified by using the modifier `↓` before the procedure name. Example.
|
||||
```lean
|
||||
simproc ↓ reduce_add (_ + _) := fun e => ...
|
||||
```
|
||||
Simplification procedures can be also scoped or local.
|
||||
-/
|
||||
syntax (docComment)? attrKind "simproc " (Tactic.simpPre <|> Tactic.simpPost)? ident " (" term ")" " := " term : command
|
||||
|
||||
/--
|
||||
A user-defined simplification procedure declaration. To activate this procedure in `simp` tactic,
|
||||
we must provide it as an argument, or use the command `attribute` to set its `[simproc]` attribute.
|
||||
-/
|
||||
syntax (docComment)? "simproc_decl " ident " (" term ")" " := " term : command
|
||||
|
||||
/--
|
||||
A builtin simplification procedure.
|
||||
-/
|
||||
syntax (docComment)? attrKind "builtin_simproc " (Tactic.simpPre <|> Tactic.simpPost)? ident " (" term ")" " := " term : command
|
||||
|
||||
/--
|
||||
A builtin simplification procedure declaration.
|
||||
-/
|
||||
syntax (docComment)? "builtin_simproc_decl " ident " (" term ")" " := " term : command
|
||||
|
||||
/--
|
||||
Auxiliary command for associating a pattern with a simplification procedure.
|
||||
-/
|
||||
syntax (name := simprocPattern) "simproc_pattern% " term " => " ident : command
|
||||
|
||||
/--
|
||||
Auxiliary command for associating a pattern with a builtin simplification procedure.
|
||||
-/
|
||||
syntax (name := simprocPatternBuiltin) "builtin_simproc_pattern% " term " => " ident : command
|
||||
|
||||
namespace Attr
|
||||
/--
|
||||
Auxiliary attribute for simplification procedures.
|
||||
-/
|
||||
syntax (name := simprocAttr) "simproc" (Tactic.simpPre <|> Tactic.simpPost)? : attr
|
||||
|
||||
/--
|
||||
Auxiliary attribute for builtin simplification procedures.
|
||||
-/
|
||||
syntax (name := simprocBuiltinAttr) "builtin_simproc" (Tactic.simpPre <|> Tactic.simpPost)? : attr
|
||||
end Attr
|
||||
|
||||
macro_rules
|
||||
| `($[$doc?:docComment]? simproc_decl $n:ident ($pattern:term) := $body) => do
|
||||
let simprocType := `Lean.Meta.Simp.Simproc
|
||||
`($[$doc?:docComment]? def $n:ident : $(mkIdent simprocType) := $body
|
||||
simproc_pattern% $pattern => $n)
|
||||
|
||||
macro_rules
|
||||
| `($[$doc?:docComment]? builtin_simproc_decl $n:ident ($pattern:term) := $body) => do
|
||||
let simprocType := `Lean.Meta.Simp.Simproc
|
||||
`($[$doc?:docComment]? def $n:ident : $(mkIdent simprocType) := $body
|
||||
builtin_simproc_pattern% $pattern => $n)
|
||||
|
||||
macro_rules
|
||||
| `($[$doc?:docComment]? $kind:attrKind simproc $[$pre?]? $n:ident ($pattern:term) := $body) => do
|
||||
`(simproc_decl $n ($pattern) := $body
|
||||
attribute [$kind simproc $[$pre?]?] $n)
|
||||
|
||||
macro_rules
|
||||
| `($[$doc?:docComment]? $kind:attrKind builtin_simproc $[$pre?]? $n:ident ($pattern:term) := $body) => do
|
||||
`(builtin_simproc_decl $n ($pattern) := $body
|
||||
attribute [$kind builtin_simproc $[$pre?]?] $n)
|
||||
|
||||
end Lean.Parser
|
||||
@@ -101,6 +101,21 @@ def withFileName (p : FilePath) (fname : String) : FilePath :=
|
||||
| none => ⟨fname⟩
|
||||
| some p => p / fname
|
||||
|
||||
/-- Appends the extension `ext` to a path `p`.
|
||||
|
||||
`ext` should not contain a leading `.`, as this function adds one.
|
||||
If `ext` is the empty string, no `.` is added.
|
||||
|
||||
Unlike `System.FilePath.withExtension`, this does not remove any existing extension. -/
|
||||
def addExtension (p : FilePath) (ext : String) : FilePath :=
|
||||
match p.fileName with
|
||||
| none => p
|
||||
| some fname => p.withFileName (if ext.isEmpty then fname else fname ++ "." ++ ext)
|
||||
|
||||
/-- Replace the current extension in a path `p` with `ext`.
|
||||
|
||||
`ext` should not contain a `.`, as this function adds one.
|
||||
If `ext` is the empty string, no `.` is added. -/
|
||||
def withExtension (p : FilePath) (ext : String) : FilePath :=
|
||||
match p.fileStem with
|
||||
| none => p
|
||||
|
||||
@@ -117,20 +117,23 @@ opaque asTask (act : BaseIO α) (prio := Task.Priority.default) : BaseIO (Task
|
||||
|
||||
/-- See `BaseIO.asTask`. -/
|
||||
@[extern "lean_io_map_task"]
|
||||
opaque mapTask (f : α → BaseIO β) (t : Task α) (prio := Task.Priority.default) : BaseIO (Task β) :=
|
||||
opaque mapTask (f : α → BaseIO β) (t : Task α) (prio := Task.Priority.default) (sync := false) :
|
||||
BaseIO (Task β) :=
|
||||
Task.pure <$> f t.get
|
||||
|
||||
/-- See `BaseIO.asTask`. -/
|
||||
@[extern "lean_io_bind_task"]
|
||||
opaque bindTask (t : Task α) (f : α → BaseIO (Task β)) (prio := Task.Priority.default) : BaseIO (Task β) :=
|
||||
opaque bindTask (t : Task α) (f : α → BaseIO (Task β)) (prio := Task.Priority.default)
|
||||
(sync := false) : BaseIO (Task β) :=
|
||||
f t.get
|
||||
|
||||
def mapTasks (f : List α → BaseIO β) (tasks : List (Task α)) (prio := Task.Priority.default) : BaseIO (Task β) :=
|
||||
def mapTasks (f : List α → BaseIO β) (tasks : List (Task α)) (prio := Task.Priority.default)
|
||||
(sync := false) : BaseIO (Task β) :=
|
||||
go tasks []
|
||||
where
|
||||
go
|
||||
| t::ts, as =>
|
||||
BaseIO.bindTask t (fun a => go ts (a :: as)) prio
|
||||
BaseIO.bindTask t (fun a => go ts (a :: as)) prio sync
|
||||
| [], as => f as.reverse |>.asTask prio
|
||||
|
||||
end BaseIO
|
||||
@@ -142,16 +145,20 @@ namespace EIO
|
||||
act.toBaseIO.asTask prio
|
||||
|
||||
/-- `EIO` specialization of `BaseIO.mapTask`. -/
|
||||
@[inline] def mapTask (f : α → EIO ε β) (t : Task α) (prio := Task.Priority.default) : BaseIO (Task (Except ε β)) :=
|
||||
BaseIO.mapTask (fun a => f a |>.toBaseIO) t prio
|
||||
@[inline] def mapTask (f : α → EIO ε β) (t : Task α) (prio := Task.Priority.default)
|
||||
(sync := false) : BaseIO (Task (Except ε β)) :=
|
||||
BaseIO.mapTask (fun a => f a |>.toBaseIO) t prio sync
|
||||
|
||||
/-- `EIO` specialization of `BaseIO.bindTask`. -/
|
||||
@[inline] def bindTask (t : Task α) (f : α → EIO ε (Task (Except ε β))) (prio := Task.Priority.default) : BaseIO (Task (Except ε β)) :=
|
||||
BaseIO.bindTask t (fun a => f a |>.catchExceptions fun e => return Task.pure <| Except.error e) prio
|
||||
@[inline] def bindTask (t : Task α) (f : α → EIO ε (Task (Except ε β)))
|
||||
(prio := Task.Priority.default) (sync := false) : BaseIO (Task (Except ε β)) :=
|
||||
BaseIO.bindTask t (fun a => f a |>.catchExceptions fun e => return Task.pure <| Except.error e)
|
||||
prio sync
|
||||
|
||||
/-- `EIO` specialization of `BaseIO.mapTasks`. -/
|
||||
@[inline] def mapTasks (f : List α → EIO ε β) (tasks : List (Task α)) (prio := Task.Priority.default) : BaseIO (Task (Except ε β)) :=
|
||||
BaseIO.mapTasks (fun as => f as |>.toBaseIO) tasks prio
|
||||
@[inline] def mapTasks (f : List α → EIO ε β) (tasks : List (Task α))
|
||||
(prio := Task.Priority.default) (sync := false) : BaseIO (Task (Except ε β)) :=
|
||||
BaseIO.mapTasks (fun as => f as |>.toBaseIO) tasks prio sync
|
||||
|
||||
end EIO
|
||||
|
||||
@@ -184,16 +191,19 @@ def sleep (ms : UInt32) : BaseIO Unit :=
|
||||
EIO.asTask act prio
|
||||
|
||||
/-- `IO` specialization of `EIO.mapTask`. -/
|
||||
@[inline] def mapTask (f : α → IO β) (t : Task α) (prio := Task.Priority.default) : BaseIO (Task (Except IO.Error β)) :=
|
||||
EIO.mapTask f t prio
|
||||
@[inline] def mapTask (f : α → IO β) (t : Task α) (prio := Task.Priority.default) (sync := false) :
|
||||
BaseIO (Task (Except IO.Error β)) :=
|
||||
EIO.mapTask f t prio sync
|
||||
|
||||
/-- `IO` specialization of `EIO.bindTask`. -/
|
||||
@[inline] def bindTask (t : Task α) (f : α → IO (Task (Except IO.Error β))) (prio := Task.Priority.default) : BaseIO (Task (Except IO.Error β)) :=
|
||||
EIO.bindTask t f prio
|
||||
@[inline] def bindTask (t : Task α) (f : α → IO (Task (Except IO.Error β)))
|
||||
(prio := Task.Priority.default) (sync := false) : BaseIO (Task (Except IO.Error β)) :=
|
||||
EIO.bindTask t f prio sync
|
||||
|
||||
/-- `IO` specialization of `EIO.mapTasks`. -/
|
||||
@[inline] def mapTasks (f : List α → IO β) (tasks : List (Task α)) (prio := Task.Priority.default) : BaseIO (Task (Except IO.Error β)) :=
|
||||
EIO.mapTasks f tasks prio
|
||||
@[inline] def mapTasks (f : List α → IO β) (tasks : List (Task α)) (prio := Task.Priority.default)
|
||||
(sync := false) : BaseIO (Task (Except IO.Error β)) :=
|
||||
EIO.mapTasks f tasks prio sync
|
||||
|
||||
/-- Check if the task's cancellation flag has been set by calling `IO.cancel` or dropping the last reference to the task. -/
|
||||
@[extern "lean_io_check_canceled"] opaque checkCanceled : BaseIO Bool
|
||||
|
||||
@@ -5,6 +5,7 @@ Authors: Leonardo de Moura
|
||||
-/
|
||||
prelude
|
||||
import Init.Data.Nat.Basic
|
||||
import Init.Data.String.Basic
|
||||
|
||||
namespace System
|
||||
namespace Platform
|
||||
@@ -17,5 +18,10 @@ def isWindows : Bool := getIsWindows ()
|
||||
def isOSX : Bool := getIsOSX ()
|
||||
def isEmscripten : Bool := getIsEmscripten ()
|
||||
|
||||
@[extern "lean_system_platform_target"] opaque getTarget : Unit → String
|
||||
|
||||
/-- The LLVM target triple of the current platform. Empty if missing at Lean compile time. -/
|
||||
def target : String := getTarget ()
|
||||
|
||||
end Platform
|
||||
end System
|
||||
|
||||
@@ -268,8 +268,8 @@ macro "rfl'" : tactic => `(tactic| set_option smartUnfolding false in with_unfol
|
||||
/--
|
||||
`ac_rfl` proves equalities up to application of an associative and commutative operator.
|
||||
```
|
||||
instance : IsAssociative (α := Nat) (.+.) := ⟨Nat.add_assoc⟩
|
||||
instance : IsCommutative (α := Nat) (.+.) := ⟨Nat.add_comm⟩
|
||||
instance : Associative (α := Nat) (.+.) := ⟨Nat.add_assoc⟩
|
||||
instance : Commutative (α := Nat) (.+.) := ⟨Nat.add_comm⟩
|
||||
|
||||
example (a b c d : Nat) : a + b + c + d = d + (b + c) + a := by ac_rfl
|
||||
```
|
||||
@@ -559,7 +559,7 @@ You can use `with` to provide the variables names for each constructor.
|
||||
- `induction e`, where `e` is an expression instead of a variable,
|
||||
generalizes `e` in the goal, and then performs induction on the resulting variable.
|
||||
- `induction e using r` allows the user to specify the principle of induction that should be used.
|
||||
Here `r` should be a theorem whose result type must be of the form `C t`,
|
||||
Here `r` should be a term whose result type must be of the form `C t`,
|
||||
where `C` is a bound variable and `t` is a (possibly empty) sequence of bound variables
|
||||
- `induction e generalizing z₁ ... zₙ`, where `z₁ ... zₙ` are variables in the local context,
|
||||
generalizes over `z₁ ... zₙ` before applying the induction but then introduces them in each goal.
|
||||
@@ -567,7 +567,7 @@ You can use `with` to provide the variables names for each constructor.
|
||||
- Given `x : Nat`, `induction x with | zero => tac₁ | succ x' ih => tac₂`
|
||||
uses tactic `tac₁` for the `zero` case, and `tac₂` for the `succ` case.
|
||||
-/
|
||||
syntax (name := induction) "induction " term,+ (" using " ident)?
|
||||
syntax (name := induction) "induction " term,+ (" using " term)?
|
||||
(" generalizing" (ppSpace colGt term:max)+)? (inductionAlts)? : tactic
|
||||
|
||||
/-- A `generalize` argument, of the form `term = x` or `h : term = x`. -/
|
||||
@@ -610,7 +610,7 @@ You can use `with` to provide the variables names for each constructor.
|
||||
performs cases on `e` as above, but also adds a hypothesis `h : e = ...` to each hypothesis,
|
||||
where `...` is the constructor instance for that particular case.
|
||||
-/
|
||||
syntax (name := cases) "cases " casesTarget,+ (" using " ident)? (inductionAlts)? : tactic
|
||||
syntax (name := cases) "cases " casesTarget,+ (" using " term)? (inductionAlts)? : tactic
|
||||
|
||||
/-- `rename_i x_1 ... x_n` renames the last `n` inaccessible names using the given names. -/
|
||||
syntax (name := renameI) "rename_i" (ppSpace colGt binderIdent)+ : tactic
|
||||
@@ -753,7 +753,7 @@ end Tactic
|
||||
|
||||
namespace Attr
|
||||
/--
|
||||
Theorems tagged with the `simp` attribute are by the simplifier
|
||||
Theorems tagged with the `simp` attribute are used by the simplifier
|
||||
(i.e., the `simp` tactic, and its variants) to simplify expressions occurring in your goals.
|
||||
We call theorems tagged with the `simp` attribute "simp theorems" or "simp lemmas".
|
||||
Lean maintains a database/index containing all active simp theorems.
|
||||
|
||||
@@ -75,10 +75,10 @@ def eqvLetValue (e₁ e₂ : LetValue) : EqvM Bool := do
|
||||
go (i+1)
|
||||
else
|
||||
x
|
||||
termination_by params₁.size - i
|
||||
go 0
|
||||
else
|
||||
return false
|
||||
termination_by go i => params₁.size - i
|
||||
|
||||
def sortAlts (alts : Array Alt) : Array Alt :=
|
||||
alts.qsort fun
|
||||
@@ -133,4 +133,4 @@ Return `true` if `c₁` and `c₂` are alpha equivalent.
|
||||
def Code.alphaEqv (c₁ c₂ : Code) : Bool :=
|
||||
AlphaEqv.eqv c₁ c₂ |>.run {}
|
||||
|
||||
end Lean.Compiler.LCNF
|
||||
end Lean.Compiler.LCNF
|
||||
|
||||
@@ -181,8 +181,8 @@ def expandCodeDecls (decls : Array CodeDecl) (body : LetValue) : CompilerM Expr
|
||||
go (i+1) (subst.push value)
|
||||
else
|
||||
(body.toExpr.abstract xs).instantiateRev subst
|
||||
termination_by values.size - i
|
||||
return go 0 #[]
|
||||
termination_by go => values.size - i
|
||||
|
||||
/--
|
||||
Create the "key" that uniquely identifies a code specialization.
|
||||
|
||||
@@ -234,7 +234,7 @@ where
|
||||
throwError "invalid instantiateForall, too many parameters"
|
||||
else
|
||||
return type
|
||||
termination_by go i _ => ps.size - i
|
||||
termination_by ps.size - i
|
||||
|
||||
/--
|
||||
Return `true` if `type` is a predicate.
|
||||
|
||||
@@ -28,8 +28,8 @@ def filterPairsM {m} [Monad m] {α} (a : Array α) (f : α → α → m (Bool ×
|
||||
let mut numRemoved := 0
|
||||
for h1 : i in [:a.size] do for h2 : j in [i+1:a.size] do
|
||||
unless removed[i]! || removed[j]! do
|
||||
let xi := a[i]'h1.2
|
||||
let xj := a[j]'h2.2
|
||||
let xi := a[i]
|
||||
let xj := a[j]
|
||||
let (keepi, keepj) ← f xi xj
|
||||
unless keepi do
|
||||
numRemoved := numRemoved + 1
|
||||
@@ -40,7 +40,7 @@ def filterPairsM {m} [Monad m] {α} (a : Array α) (f : α → α → m (Bool ×
|
||||
let mut a' := Array.mkEmpty numRemoved
|
||||
for h : i in [:a.size] do
|
||||
unless removed[i]! do
|
||||
a' := a'.push (a[i]'h.2)
|
||||
a' := a'.push a[i]
|
||||
return a'
|
||||
|
||||
end Array
|
||||
|
||||
@@ -89,7 +89,7 @@ def moveEntries [Hashable α] (i : Nat) (source : Array (AssocList α β)) (targ
|
||||
let target := es.foldl (reinsertAux hash) target
|
||||
moveEntries (i+1) source target
|
||||
else target
|
||||
termination_by _ i source _ => source.size - i
|
||||
termination_by source.size - i
|
||||
|
||||
def expand [Hashable α] (size : Nat) (buckets : HashMapBucket α β) : HashMapImp α β :=
|
||||
let bucketsNew : HashMapBucket α β := ⟨
|
||||
@@ -227,3 +227,17 @@ def ofListWith (l : List (α × β)) (f : β → β → β) : HashMap α β :=
|
||||
match m.find? p.fst with
|
||||
| none => m.insert p.fst p.snd
|
||||
| some v => m.insert p.fst $ f v p.snd)
|
||||
end Lean.HashMap
|
||||
|
||||
/--
|
||||
Groups all elements `x`, `y` in `xs` with `key x == key y` into the same array
|
||||
`(xs.groupByKey key).find! (key x)`. Groups preserve the relative order of elements in `xs`.
|
||||
-/
|
||||
def Array.groupByKey [BEq α] [Hashable α] (key : β → α) (xs : Array β)
|
||||
: Lean.HashMap α (Array β) := Id.run do
|
||||
let mut groups := ∅
|
||||
for x in xs do
|
||||
let group := groups.findD (key x) #[]
|
||||
groups := groups.erase (key x) -- make `group` referentially unique
|
||||
groups := groups.insert (key x) (group.push x)
|
||||
return groups
|
||||
|
||||
@@ -80,7 +80,7 @@ def moveEntries [Hashable α] (i : Nat) (source : Array (List α)) (target : Has
|
||||
moveEntries (i+1) source target
|
||||
else
|
||||
target
|
||||
termination_by _ i source _ => source.size - i
|
||||
termination_by source.size - i
|
||||
|
||||
def expand [Hashable α] (size : Nat) (buckets : HashSetBucket α) : HashSetImp α :=
|
||||
let bucketsNew : HashSetBucket α := ⟨
|
||||
|
||||
@@ -74,6 +74,7 @@ structure ServerCapabilities where
|
||||
declarationProvider : Bool := false
|
||||
typeDefinitionProvider : Bool := false
|
||||
referencesProvider : Bool := false
|
||||
callHierarchyProvider : Bool := false
|
||||
renameProvider? : Option RenameOptions := none
|
||||
workspaceSymbolProvider : Bool := false
|
||||
foldingRangeProvider : Bool := false
|
||||
|
||||
@@ -8,6 +8,8 @@ Authors: Joscha Mennicken
|
||||
import Lean.Expr
|
||||
import Lean.Data.Lsp.Basic
|
||||
|
||||
set_option linter.missingDocs true -- keep it documented
|
||||
|
||||
/-! This file contains types for communication between the watchdog and the
|
||||
workers. These messages are not visible externally to users of the LSP server.
|
||||
-/
|
||||
@@ -17,17 +19,27 @@ namespace Lean.Lsp
|
||||
/-! Most reference-related types have custom FromJson/ToJson implementations to
|
||||
reduce the size of the resulting JSON. -/
|
||||
|
||||
/--
|
||||
Identifier of a reference.
|
||||
-/
|
||||
inductive RefIdent where
|
||||
/-- Named identifier. These are used in all references that are globally available. -/
|
||||
| const : Name → RefIdent
|
||||
| fvar : FVarId → RefIdent
|
||||
/-- Unnamed identifier. These are used for all local references. -/
|
||||
| fvar : FVarId → RefIdent
|
||||
deriving BEq, Hashable, Inhabited
|
||||
|
||||
namespace RefIdent
|
||||
|
||||
/-- Converts the reference identifier to a string by prefixing it with a symbol. -/
|
||||
def toString : RefIdent → String
|
||||
| RefIdent.const n => s!"c:{n}"
|
||||
| RefIdent.fvar id => s!"f:{id.name}"
|
||||
|
||||
/--
|
||||
Converts the string representation of a reference identifier back to a reference identifier.
|
||||
The string representation must have been created by `RefIdent.toString`.
|
||||
-/
|
||||
def fromString (s : String) : Except String RefIdent := do
|
||||
let sPrefix := s.take 2
|
||||
let sName := s.drop 2
|
||||
@@ -43,33 +55,92 @@ def fromString (s : String) : Except String RefIdent := do
|
||||
| "f:" => return RefIdent.fvar <| FVarId.mk name
|
||||
| _ => throw "string must start with 'c:' or 'f:'"
|
||||
|
||||
instance : FromJson RefIdent where
|
||||
fromJson?
|
||||
| (s : String) => fromString s
|
||||
| j => Except.error s!"expected a String, got {j}"
|
||||
|
||||
instance : ToJson RefIdent where
|
||||
toJson ident := toString ident
|
||||
|
||||
end RefIdent
|
||||
|
||||
/-- Information about the declaration surrounding a reference. -/
|
||||
structure RefInfo.ParentDecl where
|
||||
/-- Name of the declaration surrounding a reference. -/
|
||||
name : Name
|
||||
/-- Range of the declaration surrounding a reference. -/
|
||||
range : Lsp.Range
|
||||
/-- Selection range of the declaration surrounding a reference. -/
|
||||
selectionRange : Lsp.Range
|
||||
deriving ToJson
|
||||
|
||||
/--
|
||||
Denotes the range of a reference, as well as the parent declaration of the reference.
|
||||
If the reference is itself a declaration, then it contains no parent declaration.
|
||||
-/
|
||||
structure RefInfo.Location where
|
||||
/-- Range of the reference. -/
|
||||
range : Lsp.Range
|
||||
/-- Parent declaration of the reference. `none` if the reference is itself a declaration. -/
|
||||
parentDecl? : Option RefInfo.ParentDecl
|
||||
|
||||
/-- Definition site and usage sites of a reference. Obtained from `Lean.Server.RefInfo`. -/
|
||||
structure RefInfo where
|
||||
definition : Option Lsp.Range
|
||||
usages : Array Lsp.Range
|
||||
/-- Definition site of the reference. May be `none` when we cannot find a definition site. -/
|
||||
definition? : Option RefInfo.Location
|
||||
/-- Usage sites of the reference. -/
|
||||
usages : Array RefInfo.Location
|
||||
|
||||
instance : ToJson RefInfo where
|
||||
toJson i :=
|
||||
let rangeToList (r : Lsp.Range) : List Nat :=
|
||||
[r.start.line, r.start.character, r.end.line, r.end.character]
|
||||
let parentDeclToList (d : RefInfo.ParentDecl) : List Json :=
|
||||
let name := d.name.toString |> toJson
|
||||
let range := rangeToList d.range |>.map toJson
|
||||
let selectionRange := rangeToList d.selectionRange |>.map toJson
|
||||
[name] ++ range ++ selectionRange
|
||||
let locationToList (l : RefInfo.Location) : List Json :=
|
||||
let range := rangeToList l.range |>.map toJson
|
||||
let parentDecl := l.parentDecl?.map parentDeclToList |>.getD []
|
||||
range ++ parentDecl
|
||||
Json.mkObj [
|
||||
("definition", toJson $ i.definition.map rangeToList),
|
||||
("usages", toJson $ i.usages.map rangeToList)
|
||||
("definition", toJson $ i.definition?.map locationToList),
|
||||
("usages", toJson $ i.usages.map locationToList)
|
||||
]
|
||||
|
||||
instance : FromJson RefInfo where
|
||||
fromJson? j := do
|
||||
let listToRange (l : List Nat) : Except String Lsp.Range := match l with
|
||||
let toRange : List Nat → Except String Lsp.Range
|
||||
| [sLine, sChar, eLine, eChar] => pure ⟨⟨sLine, sChar⟩, ⟨eLine, eChar⟩⟩
|
||||
| _ => throw s!"Expected list of length 4, not {l.length}"
|
||||
let definition ← j.getObjValAs? (Option $ List Nat) "definition"
|
||||
let definition ← match definition with
|
||||
| l => throw s!"Expected list of length 4, not {l.length}"
|
||||
let toParentDecl (a : Array Json) : Except String RefInfo.ParentDecl := do
|
||||
let name := String.toName <| ← fromJson? a[0]!
|
||||
let range ← a[1:5].toArray.toList |>.mapM fromJson?
|
||||
let range ← toRange range
|
||||
let selectionRange ← a[5:].toArray.toList |>.mapM fromJson?
|
||||
let selectionRange ← toRange selectionRange
|
||||
return ⟨name, range, selectionRange⟩
|
||||
let toLocation (l : List Json) : Except String RefInfo.Location := do
|
||||
let l := l.toArray
|
||||
if l.size != 4 && l.size != 13 then
|
||||
.error "Expected list of length 4 or 13, not {l.size}"
|
||||
let range ← l[:4].toArray.toList |>.mapM fromJson?
|
||||
let range ← toRange range
|
||||
if l.size == 13 then
|
||||
let parentDecl ← toParentDecl l[4:].toArray
|
||||
return ⟨range, parentDecl⟩
|
||||
else
|
||||
return ⟨range, none⟩
|
||||
|
||||
let definition? ← j.getObjValAs? (Option $ List Json) "definition"
|
||||
let definition? ← match definition? with
|
||||
| none => pure none
|
||||
| some list => some <$> listToRange list
|
||||
let usages ← j.getObjValAs? (Array $ List Nat) "usages"
|
||||
let usages ← usages.mapM listToRange
|
||||
pure { definition, usages }
|
||||
| some list => some <$> toLocation list
|
||||
let usages ← j.getObjValAs? (Array $ List Json) "usages"
|
||||
let usages ← usages.mapM toLocation
|
||||
pure { definition?, usages }
|
||||
|
||||
/-- References from a single module/file -/
|
||||
def ModuleRefs := HashMap RefIdent RefInfo
|
||||
@@ -88,7 +159,8 @@ instance : FromJson ModuleRefs where
|
||||
Contains the file's definitions and references. -/
|
||||
structure LeanIleanInfoParams where
|
||||
/-- Version of the file these references are from. -/
|
||||
version : Nat
|
||||
version : Nat
|
||||
/-- All references for the file. -/
|
||||
references : ModuleRefs
|
||||
deriving FromJson, ToJson
|
||||
|
||||
|
||||
@@ -36,16 +36,16 @@ instance : FromJson CompletionItemKind where
|
||||
|
||||
structure InsertReplaceEdit where
|
||||
newText : String
|
||||
insert : Range
|
||||
insert : Range
|
||||
replace : Range
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure CompletionItem where
|
||||
label : String
|
||||
detail? : Option String := none
|
||||
label : String
|
||||
detail? : Option String := none
|
||||
documentation? : Option MarkupContent := none
|
||||
kind? : Option CompletionItemKind := none
|
||||
textEdit? : Option InsertReplaceEdit := none
|
||||
kind? : Option CompletionItemKind := none
|
||||
textEdit? : Option InsertReplaceEdit := none
|
||||
/-
|
||||
tags? : CompletionItemTag[]
|
||||
deprecated? : boolean
|
||||
@@ -63,7 +63,7 @@ structure CompletionItem where
|
||||
|
||||
structure CompletionList where
|
||||
isIncomplete : Bool
|
||||
items : Array CompletionItem
|
||||
items : Array CompletionItem
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure CompletionParams extends TextDocumentPositionParams where
|
||||
@@ -74,7 +74,7 @@ structure Hover where
|
||||
/- NOTE we should also accept MarkedString/MarkedString[] here
|
||||
but they are deprecated, so maybe can get away without. -/
|
||||
contents : MarkupContent
|
||||
range? : Option Range := none
|
||||
range? : Option Range := none
|
||||
deriving ToJson, FromJson
|
||||
|
||||
structure HoverParams extends TextDocumentPositionParams
|
||||
@@ -153,45 +153,76 @@ inductive SymbolKind where
|
||||
| event
|
||||
| operator
|
||||
| typeParameter
|
||||
deriving BEq, Hashable, Inhabited
|
||||
|
||||
instance : FromJson SymbolKind where
|
||||
fromJson?
|
||||
| 1 => .ok .file
|
||||
| 2 => .ok .module
|
||||
| 3 => .ok .namespace
|
||||
| 4 => .ok .package
|
||||
| 5 => .ok .class
|
||||
| 6 => .ok .method
|
||||
| 7 => .ok .property
|
||||
| 8 => .ok .field
|
||||
| 9 => .ok .constructor
|
||||
| 10 => .ok .enum
|
||||
| 11 => .ok .interface
|
||||
| 12 => .ok .function
|
||||
| 13 => .ok .variable
|
||||
| 14 => .ok .constant
|
||||
| 15 => .ok .string
|
||||
| 16 => .ok .number
|
||||
| 17 => .ok .boolean
|
||||
| 18 => .ok .array
|
||||
| 19 => .ok .object
|
||||
| 20 => .ok .key
|
||||
| 21 => .ok .null
|
||||
| 22 => .ok .enumMember
|
||||
| 23 => .ok .struct
|
||||
| 24 => .ok .event
|
||||
| 25 => .ok .operator
|
||||
| 26 => .ok .typeParameter
|
||||
| j => .error s!"invalid symbol kind {j}"
|
||||
|
||||
instance : ToJson SymbolKind where
|
||||
toJson
|
||||
| SymbolKind.file => 1
|
||||
| SymbolKind.module => 2
|
||||
| SymbolKind.namespace => 3
|
||||
| SymbolKind.package => 4
|
||||
| SymbolKind.class => 5
|
||||
| SymbolKind.method => 6
|
||||
| SymbolKind.property => 7
|
||||
| SymbolKind.field => 8
|
||||
| SymbolKind.constructor => 9
|
||||
| SymbolKind.enum => 10
|
||||
| SymbolKind.interface => 11
|
||||
| SymbolKind.function => 12
|
||||
| SymbolKind.variable => 13
|
||||
| SymbolKind.constant => 14
|
||||
| SymbolKind.string => 15
|
||||
| SymbolKind.number => 16
|
||||
| SymbolKind.boolean => 17
|
||||
| SymbolKind.array => 18
|
||||
| SymbolKind.object => 19
|
||||
| SymbolKind.key => 20
|
||||
| SymbolKind.null => 21
|
||||
| SymbolKind.enumMember => 22
|
||||
| SymbolKind.struct => 23
|
||||
| SymbolKind.event => 24
|
||||
| SymbolKind.operator => 25
|
||||
| SymbolKind.typeParameter => 26
|
||||
toJson
|
||||
| .file => 1
|
||||
| .module => 2
|
||||
| .namespace => 3
|
||||
| .package => 4
|
||||
| .class => 5
|
||||
| .method => 6
|
||||
| .property => 7
|
||||
| .field => 8
|
||||
| .constructor => 9
|
||||
| .enum => 10
|
||||
| .interface => 11
|
||||
| .function => 12
|
||||
| .variable => 13
|
||||
| .constant => 14
|
||||
| .string => 15
|
||||
| .number => 16
|
||||
| .boolean => 17
|
||||
| .array => 18
|
||||
| .object => 19
|
||||
| .key => 20
|
||||
| .null => 21
|
||||
| .enumMember => 22
|
||||
| .struct => 23
|
||||
| .event => 24
|
||||
| .operator => 25
|
||||
| .typeParameter => 26
|
||||
|
||||
structure DocumentSymbolAux (Self : Type) where
|
||||
name : String
|
||||
detail? : Option String := none
|
||||
kind : SymbolKind
|
||||
name : String
|
||||
detail? : Option String := none
|
||||
kind : SymbolKind
|
||||
-- tags? : Array SymbolTag
|
||||
range : Range
|
||||
range : Range
|
||||
selectionRange : Range
|
||||
children? : Option (Array Self) := none
|
||||
deriving ToJson
|
||||
children? : Option (Array Self) := none
|
||||
deriving FromJson, ToJson
|
||||
|
||||
inductive DocumentSymbol where
|
||||
| mk (sym : DocumentSymbolAux DocumentSymbol)
|
||||
@@ -212,18 +243,56 @@ instance : ToJson DocumentSymbolResult where
|
||||
|
||||
inductive SymbolTag where
|
||||
| deprecated
|
||||
deriving BEq, Hashable, Inhabited
|
||||
|
||||
instance : FromJson SymbolTag where
|
||||
fromJson?
|
||||
| 1 => .ok .deprecated
|
||||
| j => .error s!"unknown symbol tag {j}"
|
||||
|
||||
instance : ToJson SymbolTag where
|
||||
toJson
|
||||
| SymbolTag.deprecated => 1
|
||||
toJson
|
||||
| .deprecated => 1
|
||||
|
||||
structure SymbolInformation where
|
||||
name : String
|
||||
kind : SymbolKind
|
||||
tags : Array SymbolTag := #[]
|
||||
location : Location
|
||||
name : String
|
||||
kind : SymbolKind
|
||||
tags : Array SymbolTag := #[]
|
||||
location : Location
|
||||
containerName? : Option String := none
|
||||
deriving ToJson
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure CallHierarchyPrepareParams extends TextDocumentPositionParams
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure CallHierarchyItem where
|
||||
name : String
|
||||
kind : SymbolKind
|
||||
tags? : Option (Array SymbolTag) := none
|
||||
detail? : Option String := none
|
||||
uri : DocumentUri
|
||||
range : Range
|
||||
selectionRange : Range
|
||||
-- data? : Option unknown
|
||||
deriving FromJson, ToJson, BEq, Hashable, Inhabited
|
||||
|
||||
structure CallHierarchyIncomingCallsParams where
|
||||
item : CallHierarchyItem
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure CallHierarchyIncomingCall where
|
||||
«from» : CallHierarchyItem
|
||||
fromRanges : Array Range
|
||||
deriving FromJson, ToJson, Inhabited
|
||||
|
||||
structure CallHierarchyOutgoingCallsParams where
|
||||
item : CallHierarchyItem
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure CallHierarchyOutgoingCall where
|
||||
to : CallHierarchyItem
|
||||
fromRanges : Array Range
|
||||
deriving FromJson, ToJson, Inhabited
|
||||
|
||||
inductive SemanticTokenType where
|
||||
-- Used by Lean
|
||||
@@ -304,14 +373,14 @@ example {v : SemanticTokenModifier} : open SemanticTokenModifier in
|
||||
cases v <;> native_decide
|
||||
|
||||
structure SemanticTokensLegend where
|
||||
tokenTypes : Array String
|
||||
tokenTypes : Array String
|
||||
tokenModifiers : Array String
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure SemanticTokensOptions where
|
||||
legend : SemanticTokensLegend
|
||||
range : Bool
|
||||
full : Bool /- | {
|
||||
range : Bool
|
||||
full : Bool /- | {
|
||||
delta?: boolean;
|
||||
} -/
|
||||
deriving FromJson, ToJson
|
||||
@@ -322,12 +391,12 @@ structure SemanticTokensParams where
|
||||
|
||||
structure SemanticTokensRangeParams where
|
||||
textDocument : TextDocumentIdentifier
|
||||
range : Range
|
||||
range : Range
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure SemanticTokens where
|
||||
resultId? : Option String := none
|
||||
data : Array Nat
|
||||
data : Array Nat
|
||||
deriving FromJson, ToJson
|
||||
|
||||
structure FoldingRangeParams where
|
||||
@@ -343,12 +412,12 @@ instance : ToJson FoldingRangeKind where
|
||||
toJson
|
||||
| FoldingRangeKind.comment => "comment"
|
||||
| FoldingRangeKind.imports => "imports"
|
||||
| FoldingRangeKind.region => "region"
|
||||
| FoldingRangeKind.region => "region"
|
||||
|
||||
structure FoldingRange where
|
||||
startLine : Nat
|
||||
endLine : Nat
|
||||
kind? : Option FoldingRangeKind := none
|
||||
endLine : Nat
|
||||
kind? : Option FoldingRangeKind := none
|
||||
deriving ToJson
|
||||
|
||||
structure RenameOptions where
|
||||
|
||||
@@ -8,6 +8,7 @@ import Init.Data.String
|
||||
import Init.Data.Array
|
||||
import Lean.Data.Lsp.Basic
|
||||
import Lean.Data.Position
|
||||
import Lean.DeclarationRange
|
||||
|
||||
/-! LSP uses UTF-16 for indexing, so we need to provide some primitives
|
||||
to interact with Lean strings using UTF-16 indices. -/
|
||||
@@ -86,3 +87,13 @@ def utf8PosToLspPos (text : FileMap) (pos : String.Pos) : Lsp.Position :=
|
||||
|
||||
end FileMap
|
||||
end Lean
|
||||
|
||||
/--
|
||||
Convert the Lean `DeclarationRange` to an LSP `Range` by turning the 1-indexed line numbering into a
|
||||
0-indexed line numbering and converting the character offset within the line to a UTF-16 indexed
|
||||
offset.
|
||||
-/
|
||||
def Lean.DeclarationRange.toLspRange (r : Lean.DeclarationRange) : Lsp.Range := {
|
||||
start := ⟨r.pos.line - 1, r.charUtf16⟩
|
||||
«end» := ⟨r.endPos.line - 1, r.endCharUtf16⟩
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ def appendTrees : RBNode α β → RBNode α β → RBNode α β
|
||||
| bc => balLeft a kx vx (node black bc ky vy d)
|
||||
| a, node red b kx vx c => node red (appendTrees a b) kx vx c
|
||||
| node red a kx vx b, c => node red a kx vx (appendTrees b c)
|
||||
termination_by _ x y => x.size + y.size
|
||||
termination_by x y => x.size + y.size
|
||||
|
||||
section Erase
|
||||
|
||||
|
||||
@@ -36,13 +36,12 @@ where
|
||||
if it.atEnd then r
|
||||
else if it.curr == ' ' || it.curr == '\t' then consumeSpaces n it.next r
|
||||
else saveLine it r
|
||||
termination_by (it, 1)
|
||||
saveLine (it : String.Iterator) (r : String) : String :=
|
||||
if it.atEnd then r
|
||||
else if it.curr == '\n' then consumeSpaces n it.next (r.push '\n')
|
||||
else saveLine it.next (r.push it.curr)
|
||||
termination_by
|
||||
consumeSpaces n it r => (it, 1)
|
||||
saveLine it r => (it, 0)
|
||||
termination_by (it, 0)
|
||||
|
||||
def removeLeadingSpaces (s : String) : String :=
|
||||
let n := findLeadingSpacesSize s
|
||||
|
||||
@@ -690,10 +690,10 @@ builtin_initialize elabAsElim : TagAttribute ←
|
||||
(applicationTime := .afterCompilation)
|
||||
fun declName => do
|
||||
let go : MetaM Unit := do
|
||||
discard <| getElimInfo declName
|
||||
let info ← getConstInfo declName
|
||||
if (← hasOptAutoParams info.type) then
|
||||
throwError "[elab_as_elim] attribute cannot be used in declarations containing optional and auto parameters"
|
||||
discard <| getElimInfo declName
|
||||
go.run' {} {}
|
||||
|
||||
/-! # Eliminator-like function application elaborator -/
|
||||
@@ -937,6 +937,7 @@ def elabAppArgs (f : Expr) (namedArgs : Array NamedArg) (args : Array Arg)
|
||||
where
|
||||
/-- Return `some info` if we should elaborate as an eliminator. -/
|
||||
elabAsElim? : TermElabM (Option ElimInfo) := do
|
||||
unless (← read).heedElabAsElim do return none
|
||||
if explicit || ellipsis then return none
|
||||
let .const declName _ := f | return none
|
||||
unless (← shouldElabAsElim declName) do return none
|
||||
@@ -957,8 +958,7 @@ where
|
||||
The idea is that the contribute to motive inference. See comment at `ElamElim.Context.extraArgsPos`.
|
||||
-/
|
||||
getElabAsElimExtraArgsPos (elimInfo : ElimInfo) : MetaM (Array Nat) := do
|
||||
let cinfo ← getConstInfo elimInfo.name
|
||||
forallTelescope cinfo.type fun xs type => do
|
||||
forallTelescope elimInfo.elimType fun xs type => do
|
||||
let resultArgs := type.getAppArgs
|
||||
let mut extraArgsPos := #[]
|
||||
for i in [:xs.size] do
|
||||
|
||||
@@ -6,6 +6,7 @@ Authors: Leonardo de Moura
|
||||
import Lean.Elab.Quotation.Precheck
|
||||
import Lean.Elab.Term
|
||||
import Lean.Elab.BindersUtil
|
||||
import Lean.Elab.PreDefinition.WF.TerminationHint
|
||||
|
||||
namespace Lean.Elab.Term
|
||||
open Meta
|
||||
@@ -570,7 +571,8 @@ def expandMatchAltsIntoMatchTactic (ref : Syntax) (matchAlts : Syntax) : MacroM
|
||||
-/
|
||||
def expandMatchAltsWhereDecls (matchAltsWhereDecls : Syntax) : MacroM Syntax :=
|
||||
let matchAlts := matchAltsWhereDecls[0]
|
||||
let whereDeclsOpt := matchAltsWhereDecls[1]
|
||||
-- matchAltsWhereDecls[1] is the termination hints, collected elsewhere
|
||||
let whereDeclsOpt := matchAltsWhereDecls[2]
|
||||
let rec loop (i : Nat) (discrs : Array Syntax) : MacroM Syntax :=
|
||||
match i with
|
||||
| 0 => do
|
||||
|
||||
@@ -238,10 +238,11 @@ private def mkInfoTree (elaborator : Name) (stx : Syntax) (trees : PersistentArr
|
||||
let s ← get
|
||||
let scope := s.scopes.head!
|
||||
let tree := InfoTree.node (Info.ofCommandInfo { elaborator, stx }) trees
|
||||
return InfoTree.context {
|
||||
let ctx := PartialContextInfo.commandCtx {
|
||||
env := s.env, fileMap := ctx.fileMap, mctx := {}, currNamespace := scope.currNamespace,
|
||||
openDecls := scope.openDecls, options := scope.opts, ngen := s.ngen
|
||||
} tree
|
||||
}
|
||||
return InfoTree.context ctx tree
|
||||
|
||||
private def elabCommandUsing (s : State) (stx : Syntax) : List (KeyedDeclsAttribute.AttributeEntry CommandElab) → CommandElabM Unit
|
||||
| [] => withInfoTreeContext (mkInfoTree := mkInfoTree `no_elab stx) <| throwError "unexpected syntax{indentD stx}"
|
||||
|
||||
@@ -187,15 +187,6 @@ def elabClassInductive (modifiers : Modifiers) (stx : Syntax) : CommandElabM Uni
|
||||
let v ← classInductiveSyntaxToView modifiers stx
|
||||
elabInductiveViews #[v]
|
||||
|
||||
def getTerminationHints (stx : Syntax) : TerminationHints :=
|
||||
let decl := stx[1]
|
||||
let k := decl.getKind
|
||||
if k == ``Parser.Command.def || k == ``Parser.Command.abbrev || k == ``Parser.Command.theorem || k == ``Parser.Command.instance then
|
||||
let args := decl.getArgs
|
||||
{ terminationBy? := args[args.size - 2]!.getOptional?, decreasingBy? := args[args.size - 1]!.getOptional? }
|
||||
else
|
||||
{}
|
||||
|
||||
@[builtin_command_elab declaration]
|
||||
def elabDeclaration : CommandElab := fun stx => do
|
||||
match (← liftMacroM <| expandDeclNamespace? stx) with
|
||||
@@ -219,7 +210,7 @@ def elabDeclaration : CommandElab := fun stx => do
|
||||
let modifiers ← elabModifiers stx[0]
|
||||
elabStructure modifiers decl
|
||||
else if isDefLike decl then
|
||||
elabMutualDef #[stx] (getTerminationHints stx)
|
||||
elabMutualDef #[stx]
|
||||
else
|
||||
throwError "unexpected declaration"
|
||||
|
||||
@@ -332,21 +323,10 @@ def expandMutualPreamble : Macro := fun stx =>
|
||||
|
||||
@[builtin_command_elab «mutual»]
|
||||
def elabMutual : CommandElab := fun stx => do
|
||||
let hints := { terminationBy? := stx[3].getOptional?, decreasingBy? := stx[4].getOptional? }
|
||||
if isMutualInductive stx then
|
||||
if let some bad := hints.terminationBy? then
|
||||
throwErrorAt bad "invalid 'termination_by' in mutually inductive datatype declaration"
|
||||
if let some bad := hints.decreasingBy? then
|
||||
throwErrorAt bad "invalid 'decreasing_by' in mutually inductive datatype declaration"
|
||||
elabMutualInductive stx[1].getArgs
|
||||
else if isMutualDef stx then
|
||||
for arg in stx[1].getArgs do
|
||||
let argHints := getTerminationHints arg
|
||||
if let some bad := argHints.terminationBy? then
|
||||
throwErrorAt bad "invalid 'termination_by' in 'mutual' block, it must be used after the 'end' keyword"
|
||||
if let some bad := argHints.decreasingBy? then
|
||||
throwErrorAt bad "invalid 'decreasing_by' in 'mutual' block, it must be used after the 'end' keyword"
|
||||
elabMutualDef stx[1].getArgs hints
|
||||
elabMutualDef stx[1].getArgs
|
||||
else
|
||||
throwError "invalid mutual block: either all elements of the block must be inductive declarations, or they must all be definitions/theorems/abbrevs"
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ def mkDefViewOfOpaque (modifiers : Modifiers) (stx : Syntax) : CommandElabM DefV
|
||||
| some val => pure val
|
||||
| none =>
|
||||
let val ← if modifiers.isUnsafe then `(default_or_ofNonempty% unsafe) else `(default_or_ofNonempty%)
|
||||
pure <| mkNode ``Parser.Command.declValSimple #[ mkAtomFrom stx ":=", val ]
|
||||
`(Parser.Command.declValSimple| := $val)
|
||||
return {
|
||||
ref := stx, kind := DefKind.opaque, modifiers := modifiers,
|
||||
declId := stx[1], binders := binders, type? := some type, value := val
|
||||
|
||||
@@ -57,7 +57,10 @@ where
|
||||
let b := mkIdent (← mkFreshUserName `b)
|
||||
ctorArgs1 := ctorArgs1.push a
|
||||
ctorArgs2 := ctorArgs2.push b
|
||||
if (← inferType x).isAppOf indVal.name then
|
||||
let xType ← inferType x
|
||||
if (← isProp xType) then
|
||||
continue
|
||||
if xType.isAppOf indVal.name then
|
||||
rhs ← `($rhs && $(mkIdent auxFunName):ident $a:ident $b:ident)
|
||||
else
|
||||
rhs ← `($rhs && $a:ident == $b:ident)
|
||||
@@ -91,9 +94,9 @@ def mkMutualBlock (ctx : Context) : TermElabM Syntax := do
|
||||
$auxDefs:command*
|
||||
end)
|
||||
|
||||
private def mkBEqInstanceCmds (declNames : Array Name) : TermElabM (Array Syntax) := do
|
||||
let ctx ← mkContext "beq" declNames[0]!
|
||||
let cmds := #[← mkMutualBlock ctx] ++ (← mkInstanceCmds ctx `BEq declNames)
|
||||
private def mkBEqInstanceCmds (declName : Name) : TermElabM (Array Syntax) := do
|
||||
let ctx ← mkContext "beq" declName
|
||||
let cmds := #[← mkMutualBlock ctx] ++ (← mkInstanceCmds ctx `BEq #[declName])
|
||||
trace[Elab.Deriving.beq] "\n{cmds}"
|
||||
return cmds
|
||||
|
||||
@@ -109,14 +112,18 @@ private def mkBEqEnumCmd (name : Name): TermElabM (Array Syntax) := do
|
||||
|
||||
open Command
|
||||
|
||||
def mkBEqInstance (declName : Name) : CommandElabM Unit := do
|
||||
let cmds ← liftTermElabM <|
|
||||
if (← isEnumType declName) then
|
||||
mkBEqEnumCmd declName
|
||||
else
|
||||
mkBEqInstanceCmds declName
|
||||
cmds.forM elabCommand
|
||||
|
||||
def mkBEqInstanceHandler (declNames : Array Name) : CommandElabM Bool := do
|
||||
if declNames.size == 1 && (← isEnumType declNames[0]!) then
|
||||
let cmds ← liftTermElabM <| mkBEqEnumCmd declNames[0]!
|
||||
cmds.forM elabCommand
|
||||
return true
|
||||
else if (← declNames.allM isInductive) && declNames.size > 0 then
|
||||
let cmds ← liftTermElabM <| mkBEqInstanceCmds declNames
|
||||
cmds.forM elabCommand
|
||||
if (← declNames.allM isInductive) then
|
||||
for declName in declNames do
|
||||
mkBEqInstance declName
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
@@ -67,11 +67,12 @@ where
|
||||
let b := mkIdent (← mkFreshUserName `b)
|
||||
ctorArgs1 := ctorArgs1.push a
|
||||
ctorArgs2 := ctorArgs2.push b
|
||||
let xType ← inferType x
|
||||
let indValNum :=
|
||||
ctx.typeInfos.findIdx?
|
||||
((← inferType x).isAppOf ∘ ConstantVal.name ∘ InductiveVal.toConstantVal)
|
||||
(xType.isAppOf ∘ ConstantVal.name ∘ InductiveVal.toConstantVal)
|
||||
let recField := indValNum.map (ctx.auxFunNames[·]!)
|
||||
let isProof := (← inferType (← inferType x)).isProp
|
||||
let isProof ← isProp xType
|
||||
todo := todo.push (a, b, recField, isProof)
|
||||
patterns := patterns.push (← `(@$(mkIdent ctorName₁):ident $ctorArgs1:term*))
|
||||
patterns := patterns.push (← `(@$(mkIdent ctorName₁):ident $ctorArgs2:term*))
|
||||
@@ -186,12 +187,15 @@ def mkDecEqEnum (declName : Name) : CommandElabM Unit := do
|
||||
trace[Elab.Deriving.decEq] "\n{cmd}"
|
||||
elabCommand cmd
|
||||
|
||||
def mkDecEqInstanceHandler (declNames : Array Name) : CommandElabM Bool := do
|
||||
if (← isEnumType declNames[0]!) then
|
||||
mkDecEqEnum declNames[0]!
|
||||
def mkDecEqInstance (declName : Name) : CommandElabM Bool := do
|
||||
if (← isEnumType declName) then
|
||||
mkDecEqEnum declName
|
||||
return true
|
||||
else
|
||||
mkDecEq declNames[0]!
|
||||
mkDecEq declName
|
||||
|
||||
def mkDecEqInstanceHandler (declNames : Array Name) : CommandElabM Bool := do
|
||||
declNames.foldlM (fun b n => andM (pure b) (mkDecEqInstance n)) true
|
||||
|
||||
builtin_initialize
|
||||
registerDerivingHandler `DecidableEq mkDecEqInstanceHandler
|
||||
|
||||
@@ -19,60 +19,58 @@ def mkJsonField (n : Name) : CoreM (Bool × Term) := do
|
||||
let s₁ := s.dropRightWhile (· == '?')
|
||||
return (s != s₁, Syntax.mkStrLit s₁)
|
||||
|
||||
def mkToJsonInstanceHandler (declNames : Array Name) : CommandElabM Bool := do
|
||||
if declNames.size == 1 then
|
||||
if isStructure (← getEnv) declNames[0]! then
|
||||
let cmds ← liftTermElabM do
|
||||
let ctx ← mkContext "toJson" declNames[0]!
|
||||
let header ← mkHeader ``ToJson 1 ctx.typeInfos[0]!
|
||||
let fields := getStructureFieldsFlattened (← getEnv) declNames[0]! (includeSubobjectFields := false)
|
||||
let fields ← fields.mapM fun field => do
|
||||
let (isOptField, nm) ← mkJsonField field
|
||||
let target := mkIdent header.targetNames[0]!
|
||||
if isOptField then ``(opt $nm ($target).$(mkIdent field))
|
||||
else ``([($nm, toJson ($target).$(mkIdent field))])
|
||||
let cmd ← `(private def $(mkIdent ctx.auxFunNames[0]!):ident $header.binders:bracketedBinder* : Json :=
|
||||
mkObj <| List.join [$fields,*])
|
||||
return #[cmd] ++ (← mkInstanceCmds ctx ``ToJson declNames)
|
||||
cmds.forM elabCommand
|
||||
return true
|
||||
else
|
||||
let indVal ← getConstInfoInduct declNames[0]!
|
||||
let cmds ← liftTermElabM do
|
||||
let ctx ← mkContext "toJson" declNames[0]!
|
||||
let toJsonFuncId := mkIdent ctx.auxFunNames[0]!
|
||||
-- Return syntax to JSONify `id`, either via `ToJson` or recursively
|
||||
-- if `id`'s type is the type we're deriving for.
|
||||
let mkToJson (id : Ident) (type : Expr) : TermElabM Term := do
|
||||
if type.isAppOf indVal.name then `($toJsonFuncId:ident $id:ident)
|
||||
else ``(toJson $id:ident)
|
||||
let header ← mkHeader ``ToJson 1 ctx.typeInfos[0]!
|
||||
let discrs ← mkDiscrs header indVal
|
||||
let alts ← mkAlts indVal fun ctor args userNames => do
|
||||
let ctorStr := ctor.name.eraseMacroScopes.getString!
|
||||
match args, userNames with
|
||||
| #[], _ => ``(toJson $(quote ctorStr))
|
||||
| #[(x, t)], none => ``(mkObj [($(quote ctorStr), $(← mkToJson x t))])
|
||||
| xs, none =>
|
||||
let xs ← xs.mapM fun (x, t) => mkToJson x t
|
||||
``(mkObj [($(quote ctorStr), Json.arr #[$[$xs:term],*])])
|
||||
| xs, some userNames =>
|
||||
let xs ← xs.mapIdxM fun idx (x, t) => do
|
||||
`(($(quote userNames[idx]!.eraseMacroScopes.getString!), $(← mkToJson x t)))
|
||||
``(mkObj [($(quote ctorStr), mkObj [$[$xs:term],*])])
|
||||
let auxTerm ← `(match $[$discrs],* with $alts:matchAlt*)
|
||||
let auxCmd ←
|
||||
if ctx.usePartial then
|
||||
let letDecls ← mkLocalInstanceLetDecls ctx ``ToJson header.argNames
|
||||
let auxTerm ← mkLet letDecls auxTerm
|
||||
`(private partial def $toJsonFuncId:ident $header.binders:bracketedBinder* : Json := $auxTerm)
|
||||
else
|
||||
`(private def $toJsonFuncId:ident $header.binders:bracketedBinder* : Json := $auxTerm)
|
||||
return #[auxCmd] ++ (← mkInstanceCmds ctx ``ToJson declNames)
|
||||
cmds.forM elabCommand
|
||||
return true
|
||||
def mkToJsonInstance (declName : Name) : CommandElabM Bool := do
|
||||
if isStructure (← getEnv) declName then
|
||||
let cmds ← liftTermElabM do
|
||||
let ctx ← mkContext "toJson" declName
|
||||
let header ← mkHeader ``ToJson 1 ctx.typeInfos[0]!
|
||||
let fields := getStructureFieldsFlattened (← getEnv) declName (includeSubobjectFields := false)
|
||||
let fields ← fields.mapM fun field => do
|
||||
let (isOptField, nm) ← mkJsonField field
|
||||
let target := mkIdent header.targetNames[0]!
|
||||
if isOptField then ``(opt $nm ($target).$(mkIdent field))
|
||||
else ``([($nm, toJson ($target).$(mkIdent field))])
|
||||
let cmd ← `(private def $(mkIdent ctx.auxFunNames[0]!):ident $header.binders:bracketedBinder* : Json :=
|
||||
mkObj <| List.join [$fields,*])
|
||||
return #[cmd] ++ (← mkInstanceCmds ctx ``ToJson #[declName])
|
||||
cmds.forM elabCommand
|
||||
return true
|
||||
else
|
||||
return false
|
||||
let indVal ← getConstInfoInduct declName
|
||||
let cmds ← liftTermElabM do
|
||||
let ctx ← mkContext "toJson" declName
|
||||
let toJsonFuncId := mkIdent ctx.auxFunNames[0]!
|
||||
-- Return syntax to JSONify `id`, either via `ToJson` or recursively
|
||||
-- if `id`'s type is the type we're deriving for.
|
||||
let mkToJson (id : Ident) (type : Expr) : TermElabM Term := do
|
||||
if type.isAppOf indVal.name then `($toJsonFuncId:ident $id:ident)
|
||||
else ``(toJson $id:ident)
|
||||
let header ← mkHeader ``ToJson 1 ctx.typeInfos[0]!
|
||||
let discrs ← mkDiscrs header indVal
|
||||
let alts ← mkAlts indVal fun ctor args userNames => do
|
||||
let ctorStr := ctor.name.eraseMacroScopes.getString!
|
||||
match args, userNames with
|
||||
| #[], _ => ``(toJson $(quote ctorStr))
|
||||
| #[(x, t)], none => ``(mkObj [($(quote ctorStr), $(← mkToJson x t))])
|
||||
| xs, none =>
|
||||
let xs ← xs.mapM fun (x, t) => mkToJson x t
|
||||
``(mkObj [($(quote ctorStr), Json.arr #[$[$xs:term],*])])
|
||||
| xs, some userNames =>
|
||||
let xs ← xs.mapIdxM fun idx (x, t) => do
|
||||
`(($(quote userNames[idx]!.eraseMacroScopes.getString!), $(← mkToJson x t)))
|
||||
``(mkObj [($(quote ctorStr), mkObj [$[$xs:term],*])])
|
||||
let auxTerm ← `(match $[$discrs],* with $alts:matchAlt*)
|
||||
let auxCmd ←
|
||||
if ctx.usePartial then
|
||||
let letDecls ← mkLocalInstanceLetDecls ctx ``ToJson header.argNames
|
||||
let auxTerm ← mkLet letDecls auxTerm
|
||||
`(private partial def $toJsonFuncId:ident $header.binders:bracketedBinder* : Json := $auxTerm)
|
||||
else
|
||||
`(private def $toJsonFuncId:ident $header.binders:bracketedBinder* : Json := $auxTerm)
|
||||
return #[auxCmd] ++ (← mkInstanceCmds ctx ``ToJson #[declName])
|
||||
cmds.forM elabCommand
|
||||
return true
|
||||
|
||||
where
|
||||
mkAlts
|
||||
(indVal : InductiveVal)
|
||||
@@ -103,54 +101,51 @@ where
|
||||
let rhs ← rhs ctorInfo binders (if userNames.size == binders.size then some userNames else none)
|
||||
`(matchAltExpr| | $[$patterns:term],* => $rhs:term)
|
||||
|
||||
def mkFromJsonInstanceHandler (declNames : Array Name) : CommandElabM Bool := do
|
||||
if declNames.size == 1 then
|
||||
let declName := declNames[0]!
|
||||
if isStructure (← getEnv) declName then
|
||||
let cmds ← liftTermElabM do
|
||||
let ctx ← mkContext "fromJson" declName
|
||||
let header ← mkHeader ``FromJson 0 ctx.typeInfos[0]!
|
||||
let fields := getStructureFieldsFlattened (← getEnv) declName (includeSubobjectFields := false)
|
||||
let getters ← fields.mapM (fun field => do
|
||||
let getter ← `(getObjValAs? j _ $(Prod.snd <| ← mkJsonField field))
|
||||
let getter ← `(doElem| Except.mapError (fun s => (toString $(quote declName)) ++ "." ++ (toString $(quote field)) ++ ": " ++ s) <| $getter)
|
||||
return getter
|
||||
)
|
||||
let fields := fields.map mkIdent
|
||||
let cmd ← `(private def $(mkIdent ctx.auxFunNames[0]!):ident $header.binders:bracketedBinder* (j : Json)
|
||||
: Except String $(← mkInductiveApp ctx.typeInfos[0]! header.argNames) := do
|
||||
$[let $fields:ident ← $getters]*
|
||||
return { $[$fields:ident := $(id fields)],* })
|
||||
return #[cmd] ++ (← mkInstanceCmds ctx ``FromJson declNames)
|
||||
cmds.forM elabCommand
|
||||
return true
|
||||
else
|
||||
let indVal ← getConstInfoInduct declName
|
||||
let cmds ← liftTermElabM do
|
||||
let ctx ← mkContext "fromJson" declName
|
||||
let header ← mkHeader ``FromJson 0 ctx.typeInfos[0]!
|
||||
let fromJsonFuncId := mkIdent ctx.auxFunNames[0]!
|
||||
let alts ← mkAlts indVal fromJsonFuncId
|
||||
let mut auxTerm ← alts.foldrM (fun xs x => `(Except.orElseLazy $xs (fun _ => $x))) (← `(Except.error "no inductive constructor matched"))
|
||||
if ctx.usePartial then
|
||||
let letDecls ← mkLocalInstanceLetDecls ctx ``FromJson header.argNames
|
||||
auxTerm ← mkLet letDecls auxTerm
|
||||
-- FromJson is not structurally recursive even non-nested recursive inductives,
|
||||
-- so we also use `partial` then.
|
||||
let auxCmd ←
|
||||
if ctx.usePartial || indVal.isRec then
|
||||
`(private partial def $fromJsonFuncId:ident $header.binders:bracketedBinder* (json : Json)
|
||||
: Except String $(← mkInductiveApp ctx.typeInfos[0]! header.argNames) :=
|
||||
$auxTerm)
|
||||
else
|
||||
`(private def $fromJsonFuncId:ident $header.binders:bracketedBinder* (json : Json)
|
||||
: Except String $(← mkInductiveApp ctx.typeInfos[0]! header.argNames) :=
|
||||
$auxTerm)
|
||||
return #[auxCmd] ++ (← mkInstanceCmds ctx ``FromJson declNames)
|
||||
cmds.forM elabCommand
|
||||
return true
|
||||
def mkFromJsonInstance (declName : Name) : CommandElabM Bool := do
|
||||
if isStructure (← getEnv) declName then
|
||||
let cmds ← liftTermElabM do
|
||||
let ctx ← mkContext "fromJson" declName
|
||||
let header ← mkHeader ``FromJson 0 ctx.typeInfos[0]!
|
||||
let fields := getStructureFieldsFlattened (← getEnv) declName (includeSubobjectFields := false)
|
||||
let getters ← fields.mapM (fun field => do
|
||||
let getter ← `(getObjValAs? j _ $(Prod.snd <| ← mkJsonField field))
|
||||
let getter ← `(doElem| Except.mapError (fun s => (toString $(quote declName)) ++ "." ++ (toString $(quote field)) ++ ": " ++ s) <| $getter)
|
||||
return getter
|
||||
)
|
||||
let fields := fields.map mkIdent
|
||||
let cmd ← `(private def $(mkIdent ctx.auxFunNames[0]!):ident $header.binders:bracketedBinder* (j : Json)
|
||||
: Except String $(← mkInductiveApp ctx.typeInfos[0]! header.argNames) := do
|
||||
$[let $fields:ident ← $getters]*
|
||||
return { $[$fields:ident := $(id fields)],* })
|
||||
return #[cmd] ++ (← mkInstanceCmds ctx ``FromJson #[declName])
|
||||
cmds.forM elabCommand
|
||||
return true
|
||||
else
|
||||
return false
|
||||
let indVal ← getConstInfoInduct declName
|
||||
let cmds ← liftTermElabM do
|
||||
let ctx ← mkContext "fromJson" declName
|
||||
let header ← mkHeader ``FromJson 0 ctx.typeInfos[0]!
|
||||
let fromJsonFuncId := mkIdent ctx.auxFunNames[0]!
|
||||
let alts ← mkAlts indVal fromJsonFuncId
|
||||
let mut auxTerm ← alts.foldrM (fun xs x => `(Except.orElseLazy $xs (fun _ => $x))) (← `(Except.error "no inductive constructor matched"))
|
||||
if ctx.usePartial then
|
||||
let letDecls ← mkLocalInstanceLetDecls ctx ``FromJson header.argNames
|
||||
auxTerm ← mkLet letDecls auxTerm
|
||||
-- FromJson is not structurally recursive even non-nested recursive inductives,
|
||||
-- so we also use `partial` then.
|
||||
let auxCmd ←
|
||||
if ctx.usePartial || indVal.isRec then
|
||||
`(private partial def $fromJsonFuncId:ident $header.binders:bracketedBinder* (json : Json)
|
||||
: Except String $(← mkInductiveApp ctx.typeInfos[0]! header.argNames) :=
|
||||
$auxTerm)
|
||||
else
|
||||
`(private def $fromJsonFuncId:ident $header.binders:bracketedBinder* (json : Json)
|
||||
: Except String $(← mkInductiveApp ctx.typeInfos[0]! header.argNames) :=
|
||||
$auxTerm)
|
||||
return #[auxCmd] ++ (← mkInstanceCmds ctx ``FromJson #[declName])
|
||||
cmds.forM elabCommand
|
||||
return true
|
||||
|
||||
where
|
||||
mkAlts (indVal : InductiveVal) (fromJsonFuncId : Ident) : TermElabM (Array Term) := do
|
||||
let alts ←
|
||||
@@ -188,6 +183,12 @@ where
|
||||
let alts := alts.qsort (fun (_, x) (_, y) => x < y)
|
||||
return alts.map Prod.fst
|
||||
|
||||
def mkToJsonInstanceHandler (declNames : Array Name) : CommandElabM Bool := do
|
||||
declNames.foldlM (fun b n => andM (pure b) (mkToJsonInstance n)) true
|
||||
|
||||
def mkFromJsonInstanceHandler (declNames : Array Name) : CommandElabM Bool := do
|
||||
declNames.foldlM (fun b n => andM (pure b) (mkFromJsonInstance n)) true
|
||||
|
||||
builtin_initialize
|
||||
registerDerivingHandler ``ToJson mkToJsonInstanceHandler
|
||||
registerDerivingHandler ``FromJson mkFromJsonInstanceHandler
|
||||
|
||||
@@ -75,16 +75,17 @@ def mkHashFuncs (ctx : Context) : TermElabM Syntax := do
|
||||
auxDefs := auxDefs.push (← mkAuxFunction ctx i)
|
||||
`(mutual $auxDefs:command* end)
|
||||
|
||||
private def mkHashableInstanceCmds (declNames : Array Name) : TermElabM (Array Syntax) := do
|
||||
let ctx ← mkContext "hash" declNames[0]!
|
||||
let cmds := #[← mkHashFuncs ctx] ++ (← mkInstanceCmds ctx `Hashable declNames)
|
||||
private def mkHashableInstanceCmds (declName : Name) : TermElabM (Array Syntax) := do
|
||||
let ctx ← mkContext "hash" declName
|
||||
let cmds := #[← mkHashFuncs ctx] ++ (← mkInstanceCmds ctx `Hashable #[declName])
|
||||
trace[Elab.Deriving.hashable] "\n{cmds}"
|
||||
return cmds
|
||||
|
||||
def mkHashableHandler (declNames : Array Name) : CommandElabM Bool := do
|
||||
if (← declNames.allM isInductive) && declNames.size > 0 then
|
||||
let cmds ← liftTermElabM <| mkHashableInstanceCmds declNames
|
||||
cmds.forM elabCommand
|
||||
if (← declNames.allM isInductive) then
|
||||
for declName in declNames do
|
||||
let cmds ← liftTermElabM <| mkHashableInstanceCmds declName
|
||||
cmds.forM elabCommand
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
@@ -86,18 +86,19 @@ def mkMutualBlock (ctx : Context) : TermElabM Syntax := do
|
||||
$auxDefs:command*
|
||||
end)
|
||||
|
||||
private def mkOrdInstanceCmds (declNames : Array Name) : TermElabM (Array Syntax) := do
|
||||
let ctx ← mkContext "ord" declNames[0]!
|
||||
let cmds := #[← mkMutualBlock ctx] ++ (← mkInstanceCmds ctx `Ord declNames)
|
||||
private def mkOrdInstanceCmds (declName : Name) : TermElabM (Array Syntax) := do
|
||||
let ctx ← mkContext "ord" declName
|
||||
let cmds := #[← mkMutualBlock ctx] ++ (← mkInstanceCmds ctx `Ord #[declName])
|
||||
trace[Elab.Deriving.ord] "\n{cmds}"
|
||||
return cmds
|
||||
|
||||
open Command
|
||||
|
||||
def mkOrdInstanceHandler (declNames : Array Name) : CommandElabM Bool := do
|
||||
if (← declNames.allM isInductive) && declNames.size > 0 then
|
||||
let cmds ← liftTermElabM <| mkOrdInstanceCmds declNames
|
||||
cmds.forM elabCommand
|
||||
if (← declNames.allM isInductive) then
|
||||
for declName in declNames do
|
||||
let cmds ← liftTermElabM <| mkOrdInstanceCmds declName
|
||||
cmds.forM elabCommand
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
@@ -104,18 +104,19 @@ def mkMutualBlock (ctx : Context) : TermElabM Syntax := do
|
||||
$auxDefs:command*
|
||||
end)
|
||||
|
||||
private def mkReprInstanceCmds (declNames : Array Name) : TermElabM (Array Syntax) := do
|
||||
let ctx ← mkContext "repr" declNames[0]!
|
||||
let cmds := #[← mkMutualBlock ctx] ++ (← mkInstanceCmds ctx `Repr declNames)
|
||||
private def mkReprInstanceCmd (declName : Name) : TermElabM (Array Syntax) := do
|
||||
let ctx ← mkContext "repr" declName
|
||||
let cmds := #[← mkMutualBlock ctx] ++ (← mkInstanceCmds ctx `Repr #[declName])
|
||||
trace[Elab.Deriving.repr] "\n{cmds}"
|
||||
return cmds
|
||||
|
||||
open Command
|
||||
|
||||
def mkReprInstanceHandler (declNames : Array Name) : CommandElabM Bool := do
|
||||
if (← declNames.allM isInductive) && declNames.size > 0 then
|
||||
let cmds ← liftTermElabM <| mkReprInstanceCmds declNames
|
||||
cmds.forM elabCommand
|
||||
if (← declNames.allM isInductive) then
|
||||
for declName in declNames do
|
||||
let cmds ← liftTermElabM <| mkReprInstanceCmd declName
|
||||
cmds.forM elabCommand
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
@@ -16,8 +16,9 @@ namespace Lean.Elab.Deriving.SizeOf
|
||||
open Command
|
||||
|
||||
def mkSizeOfHandler (declNames : Array Name) : CommandElabM Bool := do
|
||||
if (← declNames.allM isInductive) && declNames.size > 0 then
|
||||
liftTermElabM <| Meta.mkSizeOfInstances declNames[0]!
|
||||
if (← declNames.allM isInductive) then
|
||||
for declName in declNames do
|
||||
liftTermElabM <| Meta.mkSizeOfInstances declName
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
@@ -1368,7 +1368,7 @@ mutual
|
||||
else
|
||||
pure doElems.toArray
|
||||
let contSeq := mkDoSeq contSeq
|
||||
let auxDo ← `(do let __discr := $val; match __discr with | $pattern:term => $contSeq | _ => $elseSeq)
|
||||
let auxDo ← `(do match $val:term with | $pattern:term => $contSeq | _ => $elseSeq)
|
||||
doSeqToCode <| getDoSeqElems (getDoSeq auxDo)
|
||||
|
||||
/-- Generate `CodeBlock` for `doReassignArrow; doElems`
|
||||
|
||||
@@ -118,7 +118,7 @@ def runFrontend
|
||||
if let some ileanFileName := ileanFileName? then
|
||||
let trees := s.commandState.infoState.trees.toArray
|
||||
let references := Lean.Server.findModuleRefs inputCtx.fileMap trees (localVars := false)
|
||||
let ilean := { module := mainModuleName, references : Lean.Server.Ilean }
|
||||
let ilean := { module := mainModuleName, references := ← references.toLspModuleRefs : Lean.Server.Ilean }
|
||||
IO.FS.writeFile ileanFileName $ Json.compress $ toJson ilean
|
||||
|
||||
pure (s.commandState.env, !s.commandState.messages.hasErrors)
|
||||
|
||||
@@ -6,11 +6,11 @@ Authors: Wojciech Nawrocki, Leonardo de Moura, Sebastian Ullrich
|
||||
-/
|
||||
import Lean.Meta.PPGoal
|
||||
|
||||
namespace Lean.Elab.ContextInfo
|
||||
namespace Lean.Elab.CommandContextInfo
|
||||
|
||||
variable [Monad m] [MonadEnv m] [MonadMCtx m] [MonadOptions m] [MonadResolveName m] [MonadNameGenerator m]
|
||||
|
||||
def saveNoFileMap : m ContextInfo := return {
|
||||
def saveNoFileMap : m CommandContextInfo := return {
|
||||
env := (← getEnv)
|
||||
fileMap := default
|
||||
mctx := (← getMCtx)
|
||||
@@ -20,11 +20,32 @@ def saveNoFileMap : m ContextInfo := return {
|
||||
ngen := (← getNGen)
|
||||
}
|
||||
|
||||
def save [MonadFileMap m] : m ContextInfo := do
|
||||
def save [MonadFileMap m] : m CommandContextInfo := do
|
||||
let ctx ← saveNoFileMap
|
||||
return { ctx with fileMap := (← getFileMap) }
|
||||
|
||||
end ContextInfo
|
||||
end CommandContextInfo
|
||||
|
||||
/--
|
||||
Merges the `inner` partial context into the `outer` context s.t. fields of the `inner` context
|
||||
overwrite fields of the `outer` context. Panics if the invariant described in the documentation
|
||||
for `PartialContextInfo` is violated.
|
||||
|
||||
When traversing an `InfoTree`, this function should be used to combine the context of outer
|
||||
nodes with the partial context of their subtrees. This ensures that the traversal has the context
|
||||
from the inner node to the root node of the `InfoTree` available, with partial contexts of
|
||||
inner nodes taking priority over contexts of outer nodes.
|
||||
-/
|
||||
def PartialContextInfo.mergeIntoOuter?
|
||||
: (inner : PartialContextInfo) → (outer? : Option ContextInfo) → Option ContextInfo
|
||||
| .commandCtx info, none =>
|
||||
some { info with }
|
||||
| .parentDeclCtx _, none =>
|
||||
panic! "Unexpected incomplete InfoTree context info."
|
||||
| .commandCtx innerInfo, some outer =>
|
||||
some { outer with toCommandContextInfo := innerInfo }
|
||||
| .parentDeclCtx innerParentDecl, some outer =>
|
||||
some { outer with parentDecl? := innerParentDecl }
|
||||
|
||||
def CompletionInfo.stx : CompletionInfo → Syntax
|
||||
| dot i .. => i.stx
|
||||
@@ -197,7 +218,7 @@ def Info.updateContext? : Option ContextInfo → Info → Option ContextInfo
|
||||
partial def InfoTree.format (tree : InfoTree) (ctx? : Option ContextInfo := none) : IO Format := do
|
||||
match tree with
|
||||
| hole id => return .nestD f!"• ?{toString id.name}"
|
||||
| context i t => format t i
|
||||
| context i t => format t <| i.mergeIntoOuter? ctx?
|
||||
| node i cs => match ctx? with
|
||||
| none => return "• <context-not-available>"
|
||||
| some ctx =>
|
||||
@@ -308,20 +329,52 @@ def withInfoTreeContext [MonadFinally m] (x : m α) (mkInfoTree : PersistentArra
|
||||
@[inline] def withInfoContext [MonadFinally m] (x : m α) (mkInfo : m Info) : m α := do
|
||||
withInfoTreeContext x (fun trees => do return InfoTree.node (← mkInfo) trees)
|
||||
|
||||
/-- Resets the trees state `t₀`, runs `x` to produce a new trees
|
||||
state `t₁` and sets the state to be `t₀ ++ (InfoTree.context Γ <$> t₁)`
|
||||
where `Γ` is the context derived from the monad state. -/
|
||||
def withSaveInfoContext [MonadNameGenerator m] [MonadFinally m] [MonadEnv m] [MonadOptions m] [MonadMCtx m] [MonadResolveName m] [MonadFileMap m] (x : m α) : m α := do
|
||||
if (← getInfoState).enabled then
|
||||
let treesSaved ← getResetInfoTrees
|
||||
Prod.fst <$> MonadFinally.tryFinally' x fun _ => do
|
||||
let st ← getInfoState
|
||||
let trees ← st.trees.mapM fun tree => do
|
||||
let tree := tree.substitute st.assignment
|
||||
pure <| InfoTree.context (← ContextInfo.save) tree
|
||||
modifyInfoTrees fun _ => treesSaved ++ trees
|
||||
else
|
||||
x
|
||||
private def withSavedPartialInfoContext [MonadFinally m]
|
||||
(x : m α)
|
||||
(ctx? : m (Option PartialContextInfo))
|
||||
: m α := do
|
||||
if !(← getInfoState).enabled then
|
||||
return ← x
|
||||
let treesSaved ← getResetInfoTrees
|
||||
Prod.fst <$> MonadFinally.tryFinally' x fun _ => do
|
||||
let st ← getInfoState
|
||||
let trees ← st.trees.mapM fun tree => do
|
||||
let tree := tree.substitute st.assignment
|
||||
match (← ctx?) with
|
||||
| none =>
|
||||
pure tree
|
||||
| some ctx =>
|
||||
pure <| InfoTree.context ctx tree
|
||||
modifyInfoTrees fun _ => treesSaved ++ trees
|
||||
|
||||
/--
|
||||
Resets the trees state `t₀`, runs `x` to produce a new trees state `t₁` and sets the state to be
|
||||
`t₀ ++ (InfoTree.context (PartialContextInfo.commandCtx Γ) <$> t₁)` where `Γ` is the context derived
|
||||
from the monad state.
|
||||
-/
|
||||
def withSaveInfoContext
|
||||
[MonadNameGenerator m]
|
||||
[MonadFinally m]
|
||||
[MonadEnv m]
|
||||
[MonadOptions m]
|
||||
[MonadMCtx m]
|
||||
[MonadResolveName m]
|
||||
[MonadFileMap m]
|
||||
(x : m α)
|
||||
: m α := do
|
||||
withSavedPartialInfoContext x do
|
||||
return some <| .commandCtx (← CommandContextInfo.save)
|
||||
|
||||
/--
|
||||
Resets the trees state `t₀`, runs `x` to produce a new trees state `t₁` and sets the state to be
|
||||
`t₀ ++ (InfoTree.context (PartialContextInfo.parentDeclCtx Γ) <$> t₁)` where `Γ` is the parent decl
|
||||
name provided by `MonadParentDecl m`.
|
||||
-/
|
||||
def withSaveParentDeclInfoContext [MonadFinally m] [MonadParentDecl m] (x : m α) : m α := do
|
||||
withSavedPartialInfoContext x do
|
||||
let some declName ← getParentDeclName?
|
||||
| return none
|
||||
return some <| .parentDeclCtx declName
|
||||
|
||||
def getInfoHoleIdAssignment? (mvarId : MVarId) : m (Option InfoTree) :=
|
||||
return (← getInfoState).assignment[mvarId]
|
||||
|
||||
@@ -14,10 +14,12 @@ import Lean.Widget.Types
|
||||
|
||||
namespace Lean.Elab
|
||||
|
||||
/-- Context after executing `liftTermElabM`.
|
||||
Note that the term information collected during elaboration may contain metavariables, and their
|
||||
assignments are stored at `mctx`. -/
|
||||
structure ContextInfo where
|
||||
/--
|
||||
Context after executing `liftTermElabM`.
|
||||
Note that the term information collected during elaboration may contain metavariables, and their
|
||||
assignments are stored at `mctx`.
|
||||
-/
|
||||
structure CommandContextInfo where
|
||||
env : Environment
|
||||
fileMap : FileMap
|
||||
mctx : MetavarContext := {}
|
||||
@@ -26,6 +28,31 @@ structure ContextInfo where
|
||||
openDecls : List OpenDecl := []
|
||||
ngen : NameGenerator -- We must save the name generator to implement `ContextInfo.runMetaM` and making we not create `MVarId`s used in `mctx`.
|
||||
|
||||
/--
|
||||
Context from the root of the `InfoTree` up to this node.
|
||||
Note that the term information collected during elaboration may contain metavariables, and their
|
||||
assignments are stored at `mctx`.
|
||||
-/
|
||||
structure ContextInfo extends CommandContextInfo where
|
||||
parentDecl? : Option Name := none
|
||||
|
||||
/--
|
||||
Context for a sub-`InfoTree`.
|
||||
|
||||
Within `InfoTree`, this must fulfill the invariant that every non-`commandCtx` `PartialContextInfo`
|
||||
node is always contained within a `commandCtx` node.
|
||||
-/
|
||||
inductive PartialContextInfo where
|
||||
| commandCtx (info : CommandContextInfo)
|
||||
/--
|
||||
Context for the name of the declaration that surrounds nodes contained within this `context` node.
|
||||
For example, this makes the name of the surrounding declaration available in `InfoTree` nodes
|
||||
corresponding to the terms within the declaration.
|
||||
-/
|
||||
| parentDeclCtx (parentDecl : Name)
|
||||
-- TODO: More constructors for the different kinds of scopes `commandCtx` is currently
|
||||
-- used for (e.g. eliminating `Info.updateContext?` would be nice!).
|
||||
|
||||
/-- Base structure for `TermInfo`, `CommandInfo` and `TacticInfo`. -/
|
||||
structure ElabInfo where
|
||||
/-- The name of the elaborator that created this info. -/
|
||||
@@ -164,8 +191,8 @@ inductive Info where
|
||||
`hole`s which are filled in later in the same way that unassigned metavariables are.
|
||||
-/
|
||||
inductive InfoTree where
|
||||
/-- The context object is created by `liftTermElabM` at `Command.lean` -/
|
||||
| context (i : ContextInfo) (t : InfoTree)
|
||||
/-- The context object is created at appropriate points during elaboration -/
|
||||
| context (i : PartialContextInfo) (t : InfoTree)
|
||||
/-- The children contain information for nested term elaboration and tactic evaluation -/
|
||||
| node (i : Info) (children : PersistentArray InfoTree)
|
||||
/-- The elaborator creates holes (aka metavariables) for tactics and postponed terms -/
|
||||
@@ -191,7 +218,7 @@ structure InfoState where
|
||||
trees : PersistentArray InfoTree := {}
|
||||
deriving Inhabited
|
||||
|
||||
class MonadInfoTree (m : Type → Type) where
|
||||
class MonadInfoTree (m : Type → Type) where
|
||||
getInfoState : m InfoState
|
||||
modifyInfoState : (InfoState → InfoState) → m Unit
|
||||
|
||||
@@ -204,4 +231,9 @@ instance [MonadLift m n] [MonadInfoTree m] : MonadInfoTree n where
|
||||
def setInfoState [MonadInfoTree m] (s : InfoState) : m Unit :=
|
||||
modifyInfoState fun _ => s
|
||||
|
||||
class MonadParentDecl (m : Type → Type) where
|
||||
getParentDeclName? : m (Option Name)
|
||||
|
||||
export MonadParentDecl (getParentDeclName?)
|
||||
|
||||
end Lean.Elab
|
||||
|
||||
@@ -21,6 +21,7 @@ structure LetRecDeclView where
|
||||
type : Expr
|
||||
mvar : Expr -- auxiliary metavariable used to lift the 'let rec'
|
||||
valStx : Syntax
|
||||
termination : WF.TerminationHints
|
||||
|
||||
structure LetRecView where
|
||||
decls : Array LetRecDeclView
|
||||
@@ -59,7 +60,9 @@ private def mkLetRecDeclView (letRec : Syntax) : TermElabM LetRecView := do
|
||||
pure decl[4]
|
||||
else
|
||||
liftMacroM <| expandMatchAltsIntoMatch decl decl[3]
|
||||
pure { ref := declId, attrs, shortDeclName, declName, binderIds, type, mvar, valStx : LetRecDeclView }
|
||||
let termination ← WF.elabTerminationHints ⟨attrDeclStx[3]⟩
|
||||
pure { ref := declId, attrs, shortDeclName, declName, binderIds, type, mvar, valStx,
|
||||
termination : LetRecDeclView }
|
||||
else
|
||||
throwUnsupportedSyntax
|
||||
return { decls, body := letRec[3] }
|
||||
@@ -91,18 +94,23 @@ private def registerLetRecsToLift (views : Array LetRecDeclView) (fvars : Array
|
||||
throwError "'{view.declName}' has already been declared"
|
||||
let lctx ← getLCtx
|
||||
let localInstances ← getLocalInstances
|
||||
let toLift := views.mapIdx fun i view => {
|
||||
ref := view.ref
|
||||
fvarId := fvars[i]!.fvarId!
|
||||
attrs := view.attrs
|
||||
shortDeclName := view.shortDeclName
|
||||
declName := view.declName
|
||||
lctx
|
||||
localInstances
|
||||
type := view.type
|
||||
val := values[i]!
|
||||
mvarId := view.mvar.mvarId!
|
||||
: LetRecToLift }
|
||||
|
||||
let toLift ← views.mapIdxM fun i view => do
|
||||
let value := values[i]!
|
||||
let termination ← view.termination.checkVars view.binderIds.size value
|
||||
pure {
|
||||
ref := view.ref
|
||||
fvarId := fvars[i]!.fvarId!
|
||||
attrs := view.attrs
|
||||
shortDeclName := view.shortDeclName
|
||||
declName := view.declName
|
||||
lctx
|
||||
localInstances
|
||||
type := view.type
|
||||
val := value
|
||||
mvarId := view.mvar.mvarId!
|
||||
termination := termination
|
||||
: LetRecToLift }
|
||||
modify fun s => { s with letRecsToLift := toLift.toList ++ s.letRecsToLift }
|
||||
|
||||
@[builtin_term_elab «letrec»] def elabLetRec : TermElab := fun stx expectedType? => do
|
||||
|
||||
@@ -13,6 +13,7 @@ import Lean.Elab.Match
|
||||
import Lean.Elab.DefView
|
||||
import Lean.Elab.Deriving.Basic
|
||||
import Lean.Elab.PreDefinition.Main
|
||||
import Lean.Elab.PreDefinition.WF.TerminationHint
|
||||
import Lean.Elab.DeclarationRange
|
||||
|
||||
namespace Lean.Elab
|
||||
@@ -221,14 +222,16 @@ private def expandWhereStructInst : Macro
|
||||
/-
|
||||
Recall that
|
||||
```
|
||||
def declValSimple := leading_parser " :=\n" >> termParser >> optional Term.whereDecls
|
||||
def declValSimple := leading_parser " :=\n" >> termParser >> Termination.suffix >> optional Term.whereDecls
|
||||
def declValEqns := leading_parser Term.matchAltsWhereDecls
|
||||
def declVal := declValSimple <|> declValEqns <|> Term.whereDecls
|
||||
```
|
||||
|
||||
The `Termination.suffix` is ignored here, and extracted in `declValToTerminationHint`.
|
||||
-/
|
||||
private def declValToTerm (declVal : Syntax) : MacroM Syntax := withRef declVal do
|
||||
if declVal.isOfKind ``Parser.Command.declValSimple then
|
||||
expandWhereDeclsOpt declVal[2] declVal[1]
|
||||
expandWhereDeclsOpt declVal[3] declVal[1]
|
||||
else if declVal.isOfKind ``Parser.Command.declValEqns then
|
||||
expandMatchAltsWhereDecls declVal[0]
|
||||
else if declVal.isOfKind ``Parser.Command.whereStructInst then
|
||||
@@ -238,6 +241,15 @@ private def declValToTerm (declVal : Syntax) : MacroM Syntax := withRef declVal
|
||||
else
|
||||
Macro.throwErrorAt declVal "unexpected declaration body"
|
||||
|
||||
/-- Elaborates the termination hints in a `declVal` syntax. -/
|
||||
private def declValToTerminationHint (declVal : Syntax) : TermElabM WF.TerminationHints :=
|
||||
if declVal.isOfKind ``Parser.Command.declValSimple then
|
||||
WF.elabTerminationHints ⟨declVal[2]⟩
|
||||
else if declVal.isOfKind ``Parser.Command.declValEqns then
|
||||
WF.elabTerminationHints ⟨declVal[0][1]⟩
|
||||
else
|
||||
return .none
|
||||
|
||||
private def elabFunValues (headers : Array DefViewElabHeader) : TermElabM (Array Expr) :=
|
||||
headers.mapM fun header => withDeclName header.declName <| withLevelNames header.levelNames do
|
||||
let valStx ← liftMacroM <| declValToTerm header.valueStx
|
||||
@@ -629,6 +641,8 @@ def pushMain (preDefs : Array PreDefinition) (sectionVars : Array Expr) (mainHea
|
||||
: TermElabM (Array PreDefinition) :=
|
||||
mainHeaders.size.foldM (init := preDefs) fun i preDefs => do
|
||||
let header := mainHeaders[i]!
|
||||
let termination ← declValToTerminationHint header.valueStx
|
||||
let termination ← termination.checkVars header.numParams mainVals[i]!
|
||||
let value ← mkLambdaFVars sectionVars mainVals[i]!
|
||||
let type ← mkForallFVars sectionVars header.type
|
||||
return preDefs.push {
|
||||
@@ -637,7 +651,7 @@ def pushMain (preDefs : Array PreDefinition) (sectionVars : Array Expr) (mainHea
|
||||
declName := header.declName
|
||||
levelParams := [], -- we set it later
|
||||
modifiers := header.modifiers
|
||||
type, value
|
||||
type, value, termination
|
||||
}
|
||||
|
||||
def pushLetRecs (preDefs : Array PreDefinition) (letRecClosures : List LetRecClosure) (kind : DefKind) (modifiers : Modifiers) : MetaM (Array PreDefinition) :=
|
||||
@@ -655,7 +669,8 @@ def pushLetRecs (preDefs : Array PreDefinition) (letRecClosures : List LetRecClo
|
||||
declName := c.toLift.declName
|
||||
levelParams := [] -- we set it later
|
||||
modifiers := { modifiers with attrs := c.toLift.attrs }
|
||||
kind, type, value
|
||||
kind, type, value,
|
||||
termination := c.toLift.termination
|
||||
}
|
||||
|
||||
def getKindForLetRecs (mainHeaders : Array DefViewElabHeader) : DefKind :=
|
||||
@@ -766,7 +781,7 @@ partial def checkForHiddenUnivLevels (allUserLevelNames : List Name) (preDefs :
|
||||
for preDef in preDefs do
|
||||
checkPreDef preDef
|
||||
|
||||
def elabMutualDef (vars : Array Expr) (views : Array DefView) (hints : TerminationHints) : TermElabM Unit :=
|
||||
def elabMutualDef (vars : Array Expr) (views : Array DefView) : TermElabM Unit :=
|
||||
if isExample views then
|
||||
withoutModifyingEnv do
|
||||
-- save correct environment in info tree
|
||||
@@ -805,7 +820,7 @@ where
|
||||
for preDef in preDefs do
|
||||
trace[Elab.definition] "after eraseAuxDiscr, {preDef.declName} : {preDef.type} :=\n{preDef.value}"
|
||||
checkForHiddenUnivLevels allUserLevelNames preDefs
|
||||
addPreDefinitions preDefs hints
|
||||
addPreDefinitions preDefs
|
||||
processDeriving headers
|
||||
|
||||
processDeriving (headers : Array DefViewElabHeader) := do
|
||||
@@ -820,13 +835,13 @@ where
|
||||
end Term
|
||||
namespace Command
|
||||
|
||||
def elabMutualDef (ds : Array Syntax) (hints : TerminationHints) : CommandElabM Unit := do
|
||||
def elabMutualDef (ds : Array Syntax) : CommandElabM Unit := do
|
||||
let views ← ds.mapM fun d => do
|
||||
let modifiers ← elabModifiers d[0]
|
||||
if ds.size > 1 && modifiers.isNonrec then
|
||||
throwErrorAt d "invalid use of 'nonrec' modifier in 'mutual' block"
|
||||
mkDefView modifiers d[1]
|
||||
runTermElabM fun vars => Term.elabMutualDef vars views hints
|
||||
runTermElabM fun vars => Term.elabMutualDef vars views
|
||||
|
||||
end Command
|
||||
end Lean.Elab
|
||||
|
||||
@@ -8,11 +8,13 @@ import Lean.Util.CollectLevelParams
|
||||
import Lean.Meta.AbstractNestedProofs
|
||||
import Lean.Elab.RecAppSyntax
|
||||
import Lean.Elab.DefView
|
||||
import Lean.Elab.PreDefinition.WF.TerminationHint
|
||||
|
||||
namespace Lean.Elab
|
||||
open Meta
|
||||
open Term
|
||||
|
||||
|
||||
/--
|
||||
A (potentially recursive) definition.
|
||||
The elaborator converts it into Kernel definitions using many different strategies.
|
||||
@@ -25,6 +27,7 @@ structure PreDefinition where
|
||||
declName : Name
|
||||
type : Expr
|
||||
value : Expr
|
||||
termination : WF.TerminationHints
|
||||
deriving Inhabited
|
||||
|
||||
def instantiateMVarsAtPreDecls (preDefs : Array PreDefinition) : TermElabM (Array PreDefinition) :=
|
||||
|
||||
@@ -13,11 +13,6 @@ namespace Lean.Elab
|
||||
open Meta
|
||||
open Term
|
||||
|
||||
structure TerminationHints where
|
||||
terminationBy? : Option Syntax := none
|
||||
decreasingBy? : Option Syntax := none
|
||||
deriving Inhabited
|
||||
|
||||
private def addAndCompilePartial (preDefs : Array PreDefinition) (useSorry := false) : TermElabM Unit := do
|
||||
for preDef in preDefs do
|
||||
trace[Elab.definition] "processing {preDef.declName}"
|
||||
@@ -94,15 +89,12 @@ private def addAsAxioms (preDefs : Array PreDefinition) : TermElabM Unit := do
|
||||
applyAttributesOf #[preDef] AttributeApplicationTime.afterTypeChecking
|
||||
applyAttributesOf #[preDef] AttributeApplicationTime.afterCompilation
|
||||
|
||||
def addPreDefinitions (preDefs : Array PreDefinition) (hints : TerminationHints) : TermElabM Unit := withLCtx {} {} do
|
||||
def addPreDefinitions (preDefs : Array PreDefinition) : TermElabM Unit := withLCtx {} {} do
|
||||
for preDef in preDefs do
|
||||
trace[Elab.definition.body] "{preDef.declName} : {preDef.type} :=\n{preDef.value}"
|
||||
let preDefs ← preDefs.mapM ensureNoUnassignedMVarsAtPreDef
|
||||
let preDefs ← betaReduceLetRecApps preDefs
|
||||
let cliques := partitionPreDefs preDefs
|
||||
let mut terminationBy ← liftMacroM <| WF.expandTerminationBy? hints.terminationBy? (cliques.map fun ds => ds.map (·.declName))
|
||||
let mut decreasingBy ← liftMacroM <| WF.expandDecreasingBy? hints.decreasingBy? (cliques.map fun ds => ds.map (·.declName))
|
||||
let mut hasErrors := false
|
||||
for preDefs in cliques do
|
||||
trace[Elab.definition.scc] "{preDefs.map (·.declName)}"
|
||||
if preDefs.size == 1 && isNonRecursive preDefs[0]! then
|
||||
@@ -116,35 +108,31 @@ def addPreDefinitions (preDefs : Array PreDefinition) (hints : TerminationHints)
|
||||
addNonRec preDef
|
||||
else
|
||||
addAndCompileNonRec preDef
|
||||
preDef.termination.ensureNone "not recursive"
|
||||
else if preDefs.any (·.modifiers.isUnsafe) then
|
||||
addAndCompileUnsafe preDefs
|
||||
preDefs.forM (·.termination.ensureNone "unsafe")
|
||||
else if preDefs.any (·.modifiers.isPartial) then
|
||||
for preDef in preDefs do
|
||||
if preDef.modifiers.isPartial && !(← whnfD preDef.type).isForall then
|
||||
withRef preDef.ref <| throwError "invalid use of 'partial', '{preDef.declName}' is not a function{indentExpr preDef.type}"
|
||||
addAndCompilePartial preDefs
|
||||
preDefs.forM (·.termination.ensureNone "partial")
|
||||
else
|
||||
try
|
||||
let mut wf? := none
|
||||
let mut decrTactic? := none
|
||||
if let some wf := terminationBy.find? (preDefs.map (·.declName)) then
|
||||
wf? := some wf
|
||||
terminationBy := terminationBy.markAsUsed (preDefs.map (·.declName))
|
||||
if let some { ref, value := decrTactic } := decreasingBy.find? (preDefs.map (·.declName)) then
|
||||
decrTactic? := some (← withRef ref `(by $(⟨decrTactic⟩)))
|
||||
decreasingBy := decreasingBy.markAsUsed (preDefs.map (·.declName))
|
||||
if wf?.isSome || decrTactic?.isSome then
|
||||
wfRecursion preDefs wf? decrTactic?
|
||||
let hasHints := preDefs.any fun preDef =>
|
||||
preDef.termination.decreasing_by?.isSome || preDef.termination.termination_by?.isSome
|
||||
if hasHints then
|
||||
wfRecursion preDefs
|
||||
else
|
||||
withRef (preDefs[0]!.ref) <| mapError
|
||||
(orelseMergeErrors
|
||||
(structuralRecursion preDefs)
|
||||
(wfRecursion preDefs none none))
|
||||
(wfRecursion preDefs))
|
||||
(fun msg =>
|
||||
let preDefMsgs := preDefs.toList.map (MessageData.ofExpr $ mkConst ·.declName)
|
||||
m!"fail to show termination for{indentD (MessageData.joinSep preDefMsgs Format.line)}\nwith errors\n{msg}")
|
||||
catch ex =>
|
||||
hasErrors := true
|
||||
logException ex
|
||||
let s ← saveState
|
||||
try
|
||||
@@ -159,9 +147,6 @@ def addPreDefinitions (preDefs : Array PreDefinition) (hints : TerminationHints)
|
||||
else if preDefs.all fun preDef => preDef.kind == DefKind.theorem then
|
||||
addAsAxioms preDefs
|
||||
catch _ => s.restore
|
||||
unless hasErrors do
|
||||
liftMacroM <| terminationBy.ensureAllUsed
|
||||
liftMacroM <| decreasingBy.ensureAllUsed
|
||||
|
||||
builtin_initialize
|
||||
registerTraceClass `Elab.definition.body
|
||||
|
||||
@@ -42,7 +42,7 @@ where
|
||||
go mvarId
|
||||
else if let some mvarId ← whnfReducibleLHS? mvarId then
|
||||
go mvarId
|
||||
else match (← simpTargetStar mvarId {}).1 with
|
||||
else match (← simpTargetStar mvarId {} (simprocs := {})).1 with
|
||||
| TacticResultCNM.closed => return ()
|
||||
| TacticResultCNM.modified mvarId => go mvarId
|
||||
| TacticResultCNM.noChange =>
|
||||
|
||||
@@ -41,14 +41,12 @@ def preprocess (e : Expr) (recFnName : Name) : CoreM Expr :=
|
||||
return .visit e.headBeta
|
||||
else
|
||||
return .continue)
|
||||
(post := fun e =>
|
||||
match e with
|
||||
| .app (.mdata m f) a =>
|
||||
(post := fun e => do
|
||||
if e.isApp && e.getAppFn.isMData then
|
||||
let .mdata m f := e.getAppFn | unreachable!
|
||||
if m.isRecApp then
|
||||
return .done (.mdata m (.app f a))
|
||||
else
|
||||
return .done e
|
||||
| _ => return .done e)
|
||||
return .done (.mdata m (f.beta e.getAppArgs))
|
||||
return .continue)
|
||||
|
||||
|
||||
end Lean.Elab.Structural
|
||||
|
||||
@@ -36,73 +36,12 @@ private def rwFixEq (mvarId : MVarId) : MetaM MVarId := mvarId.withContext do
|
||||
mvarId.assign (← mkEqTrans h mvarNew)
|
||||
return mvarNew.mvarId!
|
||||
|
||||
private def hasWellFoundedFix (e : Expr) : Bool :=
|
||||
Option.isSome <| e.find? (·.isConstOf ``WellFounded.fix)
|
||||
|
||||
/--
|
||||
Helper function for decoding the packed argument for a `WellFounded.fix` application.
|
||||
Recall that we use `PSum` and `PSigma` for packing the arguments of mutually recursive nary functions.
|
||||
-/
|
||||
private partial def decodePackedArg? (info : EqnInfo) (e : Expr) : Option (Name × Array Expr) := do
|
||||
if info.declNames.size == 1 then
|
||||
let args := decodePSigma e #[]
|
||||
return (info.declNames[0]!, args)
|
||||
else
|
||||
decodePSum? e 0
|
||||
where
|
||||
decodePSum? (e : Expr) (i : Nat) : Option (Name × Array Expr) := do
|
||||
if e.isAppOfArity ``PSum.inl 3 then
|
||||
decodePSum? e.appArg! i
|
||||
else if e.isAppOfArity ``PSum.inr 3 then
|
||||
decodePSum? e.appArg! (i+1)
|
||||
else
|
||||
guard (i < info.declNames.size)
|
||||
return (info.declNames[i]!, decodePSigma e #[])
|
||||
|
||||
decodePSigma (e : Expr) (acc : Array Expr) : Array Expr :=
|
||||
/- TODO: check arity of the given function. If it takes a PSigma as the last argument,
|
||||
this function will produce incorrect results. -/
|
||||
if e.isAppOfArity ``PSigma.mk 4 then
|
||||
decodePSigma e.appArg! (acc.push e.appFn!.appArg!)
|
||||
else
|
||||
acc.push e
|
||||
|
||||
/--
|
||||
Try to fold `WellFounded.fix` applications that represent recursive applications of the functions in `info.declNames`.
|
||||
We need that to make sure `simpMatchWF?` succeeds at goals such as
|
||||
```lean
|
||||
...
|
||||
h : g x = 0
|
||||
...
|
||||
|- (match (WellFounded.fix ...) with | ...) = ...
|
||||
```
|
||||
where `WellFounded.fix ...` can be folded back to `g x`.
|
||||
-/
|
||||
private def tryToFoldWellFoundedFix (info : EqnInfo) (us : List Level) (fixedPrefix : Array Expr) (e : Expr) : MetaM Expr := do
|
||||
if hasWellFoundedFix e then
|
||||
transform e (pre := pre)
|
||||
else
|
||||
return e
|
||||
where
|
||||
pre (e : Expr) : MetaM TransformStep := do
|
||||
let e' := e.headBeta
|
||||
if e'.isAppOf ``WellFounded.fix && e'.getAppNumArgs >= 6 then
|
||||
let args := e'.getAppArgs
|
||||
let packedArg := args[5]!
|
||||
let extraArgs := args[6:]
|
||||
if let some (declName, args) := decodePackedArg? info packedArg then
|
||||
let candidate := mkAppN (mkAppN (mkAppN (mkConst declName us) fixedPrefix) args) extraArgs
|
||||
trace[Elab.definition.wf] "found nested WF at discr {candidate}"
|
||||
if (← withDefault <| isDefEq candidate e) then
|
||||
return .visit candidate
|
||||
return .continue
|
||||
|
||||
/--
|
||||
Simplify `match`-expressions when trying to prove equation theorems for a recursive declaration defined using well-founded recursion.
|
||||
It is similar to `simpMatch?`, but is also tries to fold `WellFounded.fix` applications occurring in discriminants.
|
||||
See comment at `tryToFoldWellFoundedFix`.
|
||||
-/
|
||||
def simpMatchWF? (info : EqnInfo) (us : List Level) (fixedPrefix : Array Expr) (mvarId : MVarId) : MetaM (Option MVarId) :=
|
||||
def simpMatchWF? (mvarId : MVarId) : MetaM (Option MVarId) :=
|
||||
mvarId.withContext do
|
||||
let target ← instantiateMVars (← mvarId.getType)
|
||||
let (targetNew, _) ← Simp.main target (← Split.getSimpMatchContext) (methods := { pre })
|
||||
@@ -111,32 +50,14 @@ def simpMatchWF? (info : EqnInfo) (us : List Level) (fixedPrefix : Array Expr) (
|
||||
where
|
||||
pre (e : Expr) : SimpM Simp.Step := do
|
||||
let some app ← matchMatcherApp? e | return Simp.Step.visit { expr := e }
|
||||
if app.discrs.any hasWellFoundedFix then
|
||||
let discrsNew ← app.discrs.mapM (tryToFoldWellFoundedFix info us fixedPrefix ·)
|
||||
if discrsNew != app.discrs then
|
||||
let app := { app with discrs := discrsNew }
|
||||
let eNew := app.toExpr
|
||||
trace[Elab.definition.wf] "folded discriminants {indentExpr eNew}"
|
||||
return Simp.Step.visit { expr := app.toExpr }
|
||||
-- First try to reduce matcher
|
||||
match (← reduceRecMatcher? e) with
|
||||
| some e' => return Simp.Step.done { expr := e' }
|
||||
| none =>
|
||||
match (← Simp.simpMatchCore? app e SplitIf.discharge?) with
|
||||
match (← Simp.simpMatchCore? app.matcherName e SplitIf.discharge?) with
|
||||
| some r => return r
|
||||
| none => return Simp.Step.visit { expr := e }
|
||||
|
||||
private def tryToFoldLHS? (info : EqnInfo) (us : List Level) (fixedPrefix : Array Expr) (mvarId : MVarId) : MetaM (Option MVarId) :=
|
||||
mvarId.withContext do
|
||||
let target ← mvarId.getType'
|
||||
let some (_, lhs, rhs) := target.eq? | unreachable!
|
||||
let lhsNew ← tryToFoldWellFoundedFix info us fixedPrefix lhs
|
||||
if lhs == lhsNew then return none
|
||||
let targetNew ← mkEq lhsNew rhs
|
||||
let mvarNew ← mkFreshExprSyntheticOpaqueMVar targetNew
|
||||
mvarId.assign mvarNew
|
||||
return mvarNew.mvarId!
|
||||
|
||||
/--
|
||||
Given a goal of the form `|- f.{us} a_1 ... a_n b_1 ... b_m = ...`, return `(us, #[a_1, ..., a_n])`
|
||||
where `f` is a constant named `declName`, and `n = info.fixedPrefixSize`.
|
||||
@@ -151,25 +72,24 @@ private def getFixedPrefix (declName : Name) (info : EqnInfo) (mvarId : MVarId)
|
||||
trace[Elab.definition.wf.eqns] "fixedPrefix: {result}"
|
||||
return (lhs.getAppFn.constLevels!, result)
|
||||
|
||||
private partial def mkProof (declName : Name) (info : EqnInfo) (type : Expr) : MetaM Expr := do
|
||||
private partial def mkProof (declName : Name) (type : Expr) : MetaM Expr := do
|
||||
trace[Elab.definition.wf.eqns] "proving: {type}"
|
||||
withNewMCtxDepth do
|
||||
let main ← mkFreshExprSyntheticOpaqueMVar type
|
||||
let (_, mvarId) ← main.mvarId!.intros
|
||||
let (us, fixedPrefix) ← getFixedPrefix declName info mvarId
|
||||
let rec go (mvarId : MVarId) : MetaM Unit := do
|
||||
trace[Elab.definition.wf.eqns] "step\n{MessageData.ofGoal mvarId}"
|
||||
if (← tryURefl mvarId) then
|
||||
return ()
|
||||
else if (← tryContradiction mvarId) then
|
||||
return ()
|
||||
else if let some mvarId ← simpMatchWF? info us fixedPrefix mvarId then
|
||||
else if let some mvarId ← simpMatchWF? mvarId then
|
||||
go mvarId
|
||||
else if let some mvarId ← simpIf? mvarId then
|
||||
go mvarId
|
||||
else if let some mvarId ← whnfReducibleLHS? mvarId then
|
||||
go mvarId
|
||||
else match (← simpTargetStar mvarId { config.dsimp := false }).1 with
|
||||
else match (← simpTargetStar mvarId { config.dsimp := false } (simprocs := {})).1 with
|
||||
| TacticResultCNM.closed => return ()
|
||||
| TacticResultCNM.modified mvarId => go mvarId
|
||||
| TacticResultCNM.noChange =>
|
||||
@@ -177,9 +97,10 @@ private partial def mkProof (declName : Name) (info : EqnInfo) (type : Expr) : M
|
||||
mvarIds.forM go
|
||||
else if let some mvarIds ← splitTarget? mvarId then
|
||||
mvarIds.forM go
|
||||
else if let some mvarId ← tryToFoldLHS? info us fixedPrefix mvarId then
|
||||
go mvarId
|
||||
else
|
||||
-- At some point in the past, we looked for occurences of Wf.fix to fold on the
|
||||
-- LHS (introduced in 096e4eb), but it seems that code path was never used,
|
||||
-- so #3133 removed it again (and can be recovered from there if this was premature).
|
||||
throwError "failed to generate equational theorem for '{declName}'\n{MessageData.ofGoal mvarId}"
|
||||
go (← rwFixEq (← deltaLHSUntilFix mvarId))
|
||||
instantiateMVars main
|
||||
@@ -198,7 +119,7 @@ def mkEqns (declName : Name) (info : EqnInfo) : MetaM (Array Name) :=
|
||||
trace[Elab.definition.wf.eqns] "{eqnTypes[i]!}"
|
||||
let name := baseName ++ (`_eq).appendIndexAfter (i+1)
|
||||
thmNames := thmNames.push name
|
||||
let value ← mkProof declName info type
|
||||
let value ← mkProof declName type
|
||||
let (type, value) ← removeUnusedEqnHypotheses type value
|
||||
addDecl <| Declaration.thmDecl {
|
||||
name, type, value
|
||||
|
||||
@@ -13,6 +13,7 @@ import Lean.Elab.RecAppSyntax
|
||||
import Lean.Elab.PreDefinition.Basic
|
||||
import Lean.Elab.PreDefinition.Structural.Basic
|
||||
import Lean.Elab.PreDefinition.Structural.BRecOn
|
||||
import Lean.Elab.PreDefinition.WF.PackMutual
|
||||
import Lean.Data.Array
|
||||
|
||||
namespace Lean.Elab.WF
|
||||
@@ -78,9 +79,7 @@ where
|
||||
| Expr.app .. =>
|
||||
match (← matchMatcherApp? e) with
|
||||
| some matcherApp =>
|
||||
if !Structural.recArgHasLooseBVarsAt recFnName fixedPrefixSize e then
|
||||
processApp F e
|
||||
else if let some matcherApp ← matcherApp.addArg? F then
|
||||
if let some matcherApp ← matcherApp.addArg? F then
|
||||
if !(← Structural.refinedArgType matcherApp F) then
|
||||
processApp F e
|
||||
else
|
||||
@@ -96,9 +95,7 @@ where
|
||||
| none =>
|
||||
match (← toCasesOnApp? e) with
|
||||
| some casesOnApp =>
|
||||
if !Structural.recArgHasLooseBVarsAt recFnName fixedPrefixSize e then
|
||||
processApp F e
|
||||
else if let some casesOnApp ← casesOnApp.addArg? F (checkIfRefined := true) then
|
||||
if let some casesOnApp ← casesOnApp.addArg? F (checkIfRefined := true) then
|
||||
let altsNew ← (Array.zip casesOnApp.alts casesOnApp.altNumParams).mapM fun (alt, numParams) =>
|
||||
lambdaTelescope alt fun xs altBody => do
|
||||
unless xs.size >= numParams do
|
||||
@@ -186,22 +183,48 @@ def assignSubsumed (mvars : Array MVarId) : MetaM (Array MVarId) :=
|
||||
return (false, true)
|
||||
return (true, true)
|
||||
|
||||
def solveDecreasingGoals (decrTactic? : Option Syntax) (value : Expr) : MetaM Expr := do
|
||||
/--
|
||||
The subgoals, created by `mkDecreasingProof`, are of the form `[data _recApp: rel arg param]`, where
|
||||
`param` is the `PackMutual`'ed parameter of the current function, and thus we can peek at that to
|
||||
know which function is making the call.
|
||||
The close coupling with how arguments are packed and termination goals look like is not great,
|
||||
but it works for now.
|
||||
-/
|
||||
def groupGoalsByFunction (numFuncs : Nat) (goals : Array MVarId) : MetaM (Array (Array MVarId)) := do
|
||||
let mut r := mkArray numFuncs #[]
|
||||
for goal in goals do
|
||||
let (.mdata _ (.app _ param)) ← goal.getType
|
||||
| throwError "MVar does not look like like a recursive call"
|
||||
let (funidx, _) ← unpackMutualArg numFuncs param
|
||||
r := r.modify funidx (·.push goal)
|
||||
return r
|
||||
|
||||
def solveDecreasingGoals (decrTactics : Array (Option DecreasingBy)) (value : Expr) : MetaM Expr := do
|
||||
let goals ← getMVarsNoDelayed value
|
||||
let goals ← assignSubsumed goals
|
||||
goals.forM fun goal => Lean.Elab.Term.TermElabM.run' <|
|
||||
let goalss ← groupGoalsByFunction decrTactics.size goals
|
||||
for goals in goalss, decrTactic? in decrTactics do
|
||||
Lean.Elab.Term.TermElabM.run' do
|
||||
match decrTactic? with
|
||||
| none => do
|
||||
let some ref := getRecAppSyntax? (← goal.getType)
|
||||
| throwError "MVar does not look like like a recursive call"
|
||||
for goal in goals do
|
||||
let some ref := getRecAppSyntax? (← goal.getType)
|
||||
| throwError "MVar does not look like like a recursive call"
|
||||
withRef ref <| applyDefaultDecrTactic goal
|
||||
| some decrTactic => do
|
||||
-- make info from `runTactic` available
|
||||
pushInfoTree (.hole goal)
|
||||
Term.runTactic goal decrTactic
|
||||
| some decrTactic => withRef decrTactic.ref do
|
||||
unless goals.isEmpty do -- unlikely to be empty
|
||||
-- make info from `runTactic` available
|
||||
goals.forM fun goal => pushInfoTree (.hole goal)
|
||||
let remainingGoals ← Tactic.run goals[0]! do
|
||||
Tactic.setGoals goals.toList
|
||||
Tactic.withTacticInfoContext decrTactic.ref do
|
||||
Tactic.evalTactic decrTactic.tactic
|
||||
unless remainingGoals.isEmpty do
|
||||
Term.reportUnsolvedGoals remainingGoals
|
||||
instantiateMVars value
|
||||
|
||||
def mkFix (preDef : PreDefinition) (prefixArgs : Array Expr) (wfRel : Expr) (decrTactic? : Option Syntax) : TermElabM Expr := do
|
||||
def mkFix (preDef : PreDefinition) (prefixArgs : Array Expr) (wfRel : Expr)
|
||||
(decrTactics : Array (Option DecreasingBy)) : TermElabM Expr := do
|
||||
let type ← instantiateForall preDef.type prefixArgs
|
||||
let (wfFix, varName) ← forallBoundedTelescope type (some 1) fun x type => do
|
||||
let x := x[0]!
|
||||
@@ -224,7 +247,7 @@ def mkFix (preDef : PreDefinition) (prefixArgs : Array Expr) (wfRel : Expr) (dec
|
||||
let val := preDef.value.beta (prefixArgs.push x)
|
||||
let val ← processSumCasesOn x F val fun x F val => do
|
||||
processPSigmaCasesOn x F val (replaceRecApps preDef.declName prefixArgs.size)
|
||||
let val ← solveDecreasingGoals decrTactic? val
|
||||
let val ← solveDecreasingGoals decrTactics val
|
||||
mkLambdaFVars prefixArgs (mkApp wfFix (← mkLambdaFVars #[x, F] val))
|
||||
|
||||
end Lean.Elab.WF
|
||||
|
||||
@@ -72,27 +72,42 @@ register_builtin_option showInferredTerminationBy : Bool := {
|
||||
descr := "In recursive definitions, show the inferred `termination_by` measure."
|
||||
}
|
||||
|
||||
|
||||
/--
|
||||
Given a predefinition, find good variable names for its parameters.
|
||||
Use user-given parameter names if present; use x1...xn otherwise.
|
||||
Given a predefinition, return the variabe names in the outermost lambdas.
|
||||
Includes the “fixed prefix”.
|
||||
|
||||
The length of the returned array is also used to determine the arity
|
||||
of the function, so it should match what `packDomain` does.
|
||||
-/
|
||||
def originalVarNames (preDef : PreDefinition) : MetaM (Array Name) := do
|
||||
lambdaTelescope preDef.value fun xs _ => xs.mapM (·.fvarId!.getUserName)
|
||||
|
||||
The names ought to accessible (no macro scopes) and still fresh wrt to the current environment,
|
||||
/--
|
||||
Given the original paramter names from `originalVarNames`, remove the fixed prefix and find
|
||||
good variable names to be used when talking about termination arguments:
|
||||
Use user-given parameter names if present; use x1...xn otherwise.
|
||||
|
||||
The names ought to accessible (no macro scopes) and new names fresh wrt to the current environment,
|
||||
so that with `showInferredTerminationBy` we can print them to the user reliably.
|
||||
We do that by appending `'` as needed.
|
||||
|
||||
It is possible (but unlikely without malice) that some of the user-given names
|
||||
shadow each other, and the guessed relation refers to the wrong one. In that
|
||||
case, the user gets to keep both pieces (and may have to rename variables).
|
||||
-/
|
||||
partial
|
||||
def naryVarNames (fixedPrefixSize : Nat) (preDef : PreDefinition) : MetaM (Array Name):= do
|
||||
lambdaTelescope preDef.value fun xs _ => do
|
||||
let xs := xs.extract fixedPrefixSize xs.size
|
||||
let mut ns : Array Name := #[]
|
||||
for h : i in [:xs.size] do
|
||||
let n ← (xs[i]'h.2).fvarId!.getUserName
|
||||
let n := if n.hasMacroScopes then .mkSimple s!"x{i+1}" else n
|
||||
ns := ns.push (← freshen ns n)
|
||||
return ns
|
||||
def naryVarNames (fixedPrefixSize : Nat) (xs : Array Name) : MetaM (Array Name) := do
|
||||
let xs := xs.extract fixedPrefixSize xs.size
|
||||
let mut ns : Array Name := #[]
|
||||
for h : i in [:xs.size] do
|
||||
let n := xs[i]
|
||||
let n ← if n.hasMacroScopes then
|
||||
freshen ns (.mkSimple s!"x{i+1}")
|
||||
else
|
||||
pure n
|
||||
ns := ns.push n
|
||||
return ns
|
||||
where
|
||||
freshen (ns : Array Name) (n : Name): MetaM Name := do
|
||||
if !(ns.elem n) && (← resolveGlobalName n).isEmpty then
|
||||
@@ -114,7 +129,7 @@ or `casesOn` application.
|
||||
-/
|
||||
partial def withRecApps {α} (recFnName : Name) (fixedPrefixSize : Nat) (param : Expr) (e : Expr)
|
||||
(k : Expr → Array Expr → MetaM α) : MetaM (Array α) := do
|
||||
trace[Elab.definition.wf] "withRecApps: {indentExpr e}"
|
||||
trace[Elab.definition.wf] "withRecApps (param {param}): {indentExpr e}"
|
||||
let (_, as) ← loop param e |>.run #[] |>.run' {}
|
||||
return as
|
||||
where
|
||||
@@ -163,27 +178,24 @@ where
|
||||
| Expr.app .. =>
|
||||
match (← matchMatcherApp? e) with
|
||||
| some matcherApp =>
|
||||
if !Structural.recArgHasLooseBVarsAt recFnName fixedPrefixSize e then
|
||||
processApp param e
|
||||
if let some altParams ← matcherApp.refineThrough? param then
|
||||
matcherApp.discrs.forM (loop param)
|
||||
(Array.zip matcherApp.alts (Array.zip matcherApp.altNumParams altParams)).forM
|
||||
fun (alt, altNumParam, altParam) =>
|
||||
lambdaTelescope altParam fun xs altParam => do
|
||||
-- TODO: Use boundedLambdaTelescope
|
||||
unless altNumParam = xs.size do
|
||||
throwError "unexpected `casesOn` application alternative{indentExpr alt}\nat application{indentExpr e}"
|
||||
let altBody := alt.beta xs
|
||||
loop altParam altBody
|
||||
matcherApp.remaining.forM (loop param)
|
||||
else
|
||||
if let some altParams ← matcherApp.refineThrough? param then
|
||||
(Array.zip matcherApp.alts (Array.zip matcherApp.altNumParams altParams)).forM
|
||||
fun (alt, altNumParam, altParam) =>
|
||||
lambdaTelescope altParam fun xs altParam => do
|
||||
-- TODO: Use boundedLambdaTelescope
|
||||
unless altNumParam = xs.size do
|
||||
throwError "unexpected `casesOn` application alternative{indentExpr alt}\nat application{indentExpr e}"
|
||||
let altBody := alt.beta xs
|
||||
loop altParam altBody
|
||||
else
|
||||
processApp param e
|
||||
processApp param e
|
||||
| none =>
|
||||
match (← toCasesOnApp? e) with
|
||||
| some casesOnApp =>
|
||||
if !Structural.recArgHasLooseBVarsAt recFnName fixedPrefixSize e then
|
||||
processApp param e
|
||||
else
|
||||
if let some altParams ← casesOnApp.refineThrough? param then
|
||||
if let some altParams ← casesOnApp.refineThrough? param then
|
||||
loop param casesOnApp.major
|
||||
(Array.zip casesOnApp.alts (Array.zip casesOnApp.altNumParams altParams)).forM
|
||||
fun (alt, altNumParam, altParam) =>
|
||||
lambdaTelescope altParam fun xs altParam => do
|
||||
@@ -192,8 +204,10 @@ where
|
||||
throwError "unexpected `casesOn` application alternative{indentExpr alt}\nat application{indentExpr e}"
|
||||
let altBody := alt.beta xs
|
||||
loop altParam altBody
|
||||
else
|
||||
processApp param e
|
||||
casesOnApp.remaining.forM (loop param)
|
||||
else
|
||||
trace[Elab.definition.wf] "withRecApps: casesOnApp.refineThrough? failed"
|
||||
processApp param e
|
||||
| none => processApp param e
|
||||
| e => do
|
||||
let _ ← ensureNoRecFn recFnName e
|
||||
@@ -274,12 +288,12 @@ def collectRecCalls (unaryPreDef : PreDefinition) (fixedPrefixSize : Nat) (ariti
|
||||
unless xs.size == fixedPrefixSize + 1 do
|
||||
-- Maybe cleaner to have lambdaBoundedTelescope?
|
||||
throwError "Unexpected number of lambdas in unary pre-definition"
|
||||
-- trace[Elab.definition.wf] "collectRecCalls: {xs} {body}"
|
||||
let param := xs[fixedPrefixSize]!
|
||||
withRecApps unaryPreDef.declName fixedPrefixSize param body fun param args => do
|
||||
unless args.size ≥ fixedPrefixSize + 1 do
|
||||
throwError "Insufficient arguments in recursive call"
|
||||
let arg := args[fixedPrefixSize]!
|
||||
trace[Elab.definition.wf] "collectRecCalls: {unaryPreDef.declName} ({param}) → {unaryPreDef.declName} ({arg})"
|
||||
let (caller, params) ← unpackArg arities param
|
||||
let (callee, args) ← unpackArg arities arg
|
||||
RecCallWithContext.create (← getRef) caller params callee args
|
||||
@@ -318,7 +332,7 @@ def mkSizeOf (e : Expr) : MetaM Expr := do
|
||||
For a given recursive call, and a choice of parameter and argument index,
|
||||
try to prove equality, < or ≤.
|
||||
-/
|
||||
def evalRecCall (decrTactic? : Option Syntax) (rcc : RecCallWithContext) (paramIdx argIdx : Nat) :
|
||||
def evalRecCall (decrTactic? : Option DecreasingBy) (rcc : RecCallWithContext) (paramIdx argIdx : Nat) :
|
||||
MetaM GuessLexRel := do
|
||||
rcc.ctxt.run do
|
||||
let param := rcc.params[paramIdx]!
|
||||
@@ -334,25 +348,20 @@ def evalRecCall (decrTactic? : Option Syntax) (rcc : RecCallWithContext) (paramI
|
||||
let mvar ← mkFreshExprSyntheticOpaqueMVar goalExpr
|
||||
let mvarId := mvar.mvarId!
|
||||
let mvarId ← mvarId.cleanup
|
||||
-- logInfo m!"Remaining goals: {goalsToMessageData [mvarId]}"
|
||||
try
|
||||
if rel = .eq then
|
||||
MVarId.refl mvarId
|
||||
else do
|
||||
Lean.Elab.Term.TermElabM.run' do
|
||||
match decrTactic? with
|
||||
| none =>
|
||||
let remainingGoals ← Tactic.run mvarId do
|
||||
Tactic.evalTactic (← `(tactic| decreasing_tactic))
|
||||
remainingGoals.forM fun mvarId => Term.reportUnsolvedGoals [mvarId]
|
||||
-- trace[Elab.definition.wf] "Found {rel} proof: {← instantiateMVars mvar}"
|
||||
pure ()
|
||||
| some decrTactic => Term.withoutErrToSorry do
|
||||
-- make info from `runTactic` available
|
||||
pushInfoTree (.hole mvarId)
|
||||
Term.runTactic mvarId decrTactic
|
||||
-- trace[Elab.definition.wf] "Found {rel} proof: {← instantiateMVars mvar}"
|
||||
pure ()
|
||||
Lean.Elab.Term.TermElabM.run' do Term.withoutErrToSorry do
|
||||
let remainingGoals ← Tactic.run mvarId do Tactic.withoutRecover do
|
||||
let tacticStx : Syntax ←
|
||||
match decrTactic? with
|
||||
| none => pure (← `(tactic| decreasing_tactic)).raw
|
||||
| some decrTactic =>
|
||||
trace[Elab.definition.wf] "Using tactic {decrTactic.tactic.raw}"
|
||||
pure decrTactic.tactic.raw
|
||||
Tactic.evalTactic tacticStx
|
||||
remainingGoals.forM fun _ => throwError "goal not solved"
|
||||
trace[Elab.definition.wf] "inspectRecCall: success!"
|
||||
return rel
|
||||
catch _e =>
|
||||
@@ -362,13 +371,15 @@ def evalRecCall (decrTactic? : Option Syntax) (rcc : RecCallWithContext) (paramI
|
||||
|
||||
/- A cache for `evalRecCall` -/
|
||||
structure RecCallCache where mk'' ::
|
||||
decrTactic? : Option Syntax
|
||||
decrTactic? : Option DecreasingBy
|
||||
rcc : RecCallWithContext
|
||||
cache : IO.Ref (Array (Array (Option GuessLexRel)))
|
||||
|
||||
/-- Create a cache to memoize calls to `evalRecCall descTactic? rcc` -/
|
||||
def RecCallCache.mk (decrTactic? : Option Syntax) (rcc : RecCallWithContext) :
|
||||
def RecCallCache.mk (decrTactics : Array (Option DecreasingBy))
|
||||
(rcc : RecCallWithContext) :
|
||||
BaseIO RecCallCache := do
|
||||
let decrTactic? := decrTactics[rcc.caller]!
|
||||
let cache ← IO.mkRef <| Array.mkArray rcc.params.size (Array.mkArray rcc.args.size Option.none)
|
||||
return { decrTactic?, rcc, cache }
|
||||
|
||||
@@ -505,7 +516,7 @@ partial def solve {m} {α} [Monad m] (measures : Array α)
|
||||
|
||||
-- Find the first measure that has at least one < and otherwise only = or <=
|
||||
for h : measureIdx in [:measures.size] do
|
||||
let measure := measures[measureIdx]'h.2
|
||||
let measure := measures[measureIdx]
|
||||
let mut has_lt := false
|
||||
let mut all_le := true
|
||||
let mut todo := #[]
|
||||
@@ -536,29 +547,46 @@ def mkTupleSyntax : Array Term → MetaM Term
|
||||
|
||||
/--
|
||||
Given an array of `MutualMeasures`, creates a `TerminationWF` that specifies the lexicographic
|
||||
combination of these measures.
|
||||
combination of these measures. The parameters are
|
||||
|
||||
* `originalVarNamess`: For each function in the clique, the original parameter names, _including_
|
||||
the fixed prefix. Used to determine if we need to fully qualify `sizeOf`.
|
||||
* `varNamess`: For each function in the clique, the parameter names to be used in the
|
||||
termination relation. Excludes the fixed prefix. Includes names like `x1` for unnamed parameters.
|
||||
* `measures`: The measures to be used.
|
||||
-/
|
||||
def buildTermWF (declNames : Array Name) (varNamess : Array (Array Name))
|
||||
(measures : Array MutualMeasure) : MetaM TerminationWF := do
|
||||
let mut termByElements := #[]
|
||||
for h : funIdx in [:varNamess.size] do
|
||||
let vars := (varNamess[funIdx]'h.2).map mkIdent
|
||||
let body ← mkTupleSyntax (← measures.mapM fun
|
||||
def buildTermWF (originalVarNamess : Array (Array Name)) (varNamess : Array (Array Name))
|
||||
(measures : Array MutualMeasure) : MetaM TerminationWF := do
|
||||
varNamess.mapIdxM fun funIdx varNames => do
|
||||
let idents := varNames.map mkIdent
|
||||
let measureStxs ← measures.mapM fun
|
||||
| .args varIdxs => do
|
||||
let v := vars.get! (varIdxs[funIdx]!)
|
||||
let sizeOfIdent := mkIdent (← unresolveNameGlobal ``sizeOf)
|
||||
let varIdx := varIdxs[funIdx]!
|
||||
let v := idents[varIdx]!
|
||||
-- Print `sizeOf` as such, unless it is shadowed.
|
||||
-- Shadowing by a `def` in the current namespace is handled by `unresolveNameGlobal`.
|
||||
-- But it could also be shadowed by an earlier parameter (including the fixed prefix),
|
||||
-- so look for unqualified (single tick) occurrences in `originalVarNames`
|
||||
let sizeOfIdent :=
|
||||
if originalVarNamess[funIdx]!.any (· = `sizeOf) then
|
||||
mkIdent ``sizeOf -- fully qualified
|
||||
else
|
||||
mkIdent (← unresolveNameGlobal ``sizeOf)
|
||||
`($sizeOfIdent $v)
|
||||
| .func funIdx' => if funIdx' == funIdx then `(1) else `(0)
|
||||
)
|
||||
let declName := declNames[funIdx]!
|
||||
|
||||
termByElements := termByElements.push
|
||||
{ ref := .missing
|
||||
declName, vars, body,
|
||||
implicit := true
|
||||
}
|
||||
return termByElements
|
||||
let body ← mkTupleSyntax measureStxs
|
||||
return { ref := .missing, vars := idents, body }
|
||||
|
||||
/--
|
||||
The TerminationWF produced by GuessLex may mention more variables than allowed in the surface
|
||||
syntax (in case of unnamed or shadowed parameters). So how to print this to the user? Invalid
|
||||
syntax with more information, or valid syntax with (possibly) unresolved variable names?
|
||||
The latter works fine in many cases, and is still useful to the user in the tricky corner cases, so
|
||||
we do that.
|
||||
-/
|
||||
def trimTermWF (extraParams : Array Nat) (elems : TerminationWF) : TerminationWF :=
|
||||
elems.mapIdx fun funIdx elem =>
|
||||
{ elem with vars := elem.vars[elem.vars.size - extraParams[funIdx]! : elem.vars.size] }
|
||||
|
||||
/--
|
||||
Given a matrix (row-major) of strings, arranges them in tabular form.
|
||||
@@ -567,14 +595,14 @@ Single space as column separator.
|
||||
-/
|
||||
def formatTable : Array (Array String) → String := fun xss => Id.run do
|
||||
let mut colWidths := xss[0]!.map (fun _ => 0)
|
||||
for i in [:xss.size] do
|
||||
for j in [:xss[i]!.size] do
|
||||
if xss[i]![j]!.length > colWidths[j]! then
|
||||
colWidths := colWidths.set! j xss[i]![j]!.length
|
||||
for hi : i in [:xss.size] do
|
||||
for hj : j in [:xss[i].size] do
|
||||
if xss[i][j].length > colWidths[j]! then
|
||||
colWidths := colWidths.set! j xss[i][j].length
|
||||
let mut str := ""
|
||||
for i in [:xss.size] do
|
||||
for j in [:xss[i]!.size] do
|
||||
let s := xss[i]![j]!
|
||||
for hi : i in [:xss.size] do
|
||||
for hj : j in [:xss[i].size] do
|
||||
let s := xss[i][j]
|
||||
if j > 0 then -- right-align
|
||||
for _ in [:colWidths[j]! - s.length] do
|
||||
str := str ++ " "
|
||||
@@ -582,7 +610,7 @@ def formatTable : Array (Array String) → String := fun xss => Id.run do
|
||||
if j = 0 then -- left-align
|
||||
for _ in [:colWidths[j]! - s.length] do
|
||||
str := str ++ " "
|
||||
if j + 1 < xss[i]!.size then
|
||||
if j + 1 < xss[i].size then
|
||||
str := str ++ " "
|
||||
if i + 1 < xss.size then
|
||||
str := str ++ "\n"
|
||||
@@ -668,10 +696,12 @@ Main entry point of this module:
|
||||
Try to find a lexicographic ordering of the arguments for which the recursive definition
|
||||
terminates. See the module doc string for a high-level overview.
|
||||
-/
|
||||
def guessLex (preDefs : Array PreDefinition) (unaryPreDef : PreDefinition)
|
||||
(fixedPrefixSize : Nat) (decrTactic? : Option Syntax) :
|
||||
def guessLex (preDefs : Array PreDefinition) (unaryPreDef : PreDefinition)
|
||||
(fixedPrefixSize : Nat) :
|
||||
MetaM TerminationWF := do
|
||||
let varNamess ← preDefs.mapM (naryVarNames fixedPrefixSize ·)
|
||||
let extraParamss := preDefs.map (·.termination.extraParams)
|
||||
let originalVarNamess ← preDefs.mapM originalVarNames
|
||||
let varNamess ← originalVarNamess.mapM (naryVarNames fixedPrefixSize ·)
|
||||
let arities := varNamess.map (·.size)
|
||||
trace[Elab.definition.wf] "varNames is: {varNamess}"
|
||||
|
||||
@@ -684,24 +714,22 @@ def guessLex (preDefs : Array PreDefinition) (unaryPreDef : PreDefinition)
|
||||
|
||||
-- If there is only one plausible measure, use that
|
||||
if let #[solution] := measures then
|
||||
return ← buildTermWF (preDefs.map (·.declName)) varNamess #[solution]
|
||||
return ← buildTermWF originalVarNamess varNamess #[solution]
|
||||
|
||||
-- Collect all recursive calls and extract their context
|
||||
let recCalls ← collectRecCalls unaryPreDef fixedPrefixSize arities
|
||||
let recCalls := filterSubsumed recCalls
|
||||
let rcs ← recCalls.mapM (RecCallCache.mk decrTactic? ·)
|
||||
let rcs ← recCalls.mapM (RecCallCache.mk (preDefs.map (·.termination.decreasing_by?)) ·)
|
||||
let callMatrix := rcs.map (inspectCall ·)
|
||||
|
||||
match ← liftMetaM <| solve measures callMatrix with
|
||||
| .some solution => do
|
||||
let wf ← buildTermWF (preDefs.map (·.declName)) varNamess solution
|
||||
|
||||
let wfStx ← withoutModifyingState do
|
||||
preDefs.forM (addAsAxiom ·)
|
||||
wf.unexpand
|
||||
let wf ← buildTermWF originalVarNamess varNamess solution
|
||||
|
||||
if showInferredTerminationBy.get (← getOptions) then
|
||||
logInfo m!"Inferred termination argument:{wfStx}"
|
||||
let wf' := trimTermWF extraParamss wf
|
||||
for preDef in preDefs, term in wf' do
|
||||
logInfoAt preDef.ref m!"Inferred termination argument: {← term.unexpand}"
|
||||
|
||||
return wf
|
||||
| .none =>
|
||||
|
||||
@@ -80,8 +80,9 @@ private def isOnlyOneUnaryDef (preDefs : Array PreDefinition) (fixedPrefixSize :
|
||||
else
|
||||
return false
|
||||
|
||||
def wfRecursion (preDefs : Array PreDefinition) (wf? : Option TerminationWF) (decrTactic? : Option Syntax) : TermElabM Unit := do
|
||||
let preDefs ← preDefs.mapM fun preDef => return { preDef with value := (← preprocess preDef.value) }
|
||||
def wfRecursion (preDefs : Array PreDefinition) : TermElabM Unit := do
|
||||
let preDefs ← preDefs.mapM fun preDef =>
|
||||
return { preDef with value := (← preprocess preDef.value) }
|
||||
let (unaryPreDef, fixedPrefixSize) ← withoutModifyingEnv do
|
||||
for preDef in preDefs do
|
||||
addAsAxiom preDef
|
||||
@@ -91,20 +92,29 @@ def wfRecursion (preDefs : Array PreDefinition) (wf? : Option TerminationWF) (de
|
||||
let unaryPreDefs ← packDomain fixedPrefixSize preDefsDIte
|
||||
return (← packMutual fixedPrefixSize preDefs unaryPreDefs, fixedPrefixSize)
|
||||
|
||||
let wf ←
|
||||
if let .some wf := wf? then
|
||||
pure wf
|
||||
let extraParamss := preDefs.map (·.termination.extraParams)
|
||||
let wf ← do
|
||||
let (preDefsWith, preDefsWithout) := preDefs.partition (·.termination.termination_by?.isSome)
|
||||
if preDefsWith.isEmpty then
|
||||
-- No termination_by anywhere, so guess one
|
||||
guessLex preDefs unaryPreDef fixedPrefixSize
|
||||
else if preDefsWithout.isEmpty then
|
||||
pure <| preDefsWith.map (·.termination.termination_by?.get!)
|
||||
else
|
||||
guessLex preDefs unaryPreDef fixedPrefixSize decrTactic?
|
||||
-- Some have, some do not, so report errors
|
||||
preDefsWithout.forM fun preDef => do
|
||||
logErrorAt preDef.ref (m!"Missing `termination_by`; this function is mutually " ++
|
||||
m!"recursive with {preDefsWith[0]!.declName}, which has a `termination_by` clause.")
|
||||
return
|
||||
|
||||
let preDefNonRec ← forallBoundedTelescope unaryPreDef.type fixedPrefixSize fun prefixArgs type => do
|
||||
let type ← whnfForall type
|
||||
let packedArgType := type.bindingDomain!
|
||||
elabWFRel preDefs unaryPreDef.declName fixedPrefixSize packedArgType wf fun wfRel => do
|
||||
elabWFRel preDefs unaryPreDef.declName fixedPrefixSize packedArgType extraParamss wf fun wfRel => do
|
||||
trace[Elab.definition.wf] "wfRel: {wfRel}"
|
||||
let (value, envNew) ← withoutModifyingEnv' do
|
||||
addAsAxiom unaryPreDef
|
||||
let value ← mkFix unaryPreDef prefixArgs wfRel decrTactic?
|
||||
let value ← mkFix unaryPreDef prefixArgs wfRel (preDefs.map (·.termination.decreasing_by?))
|
||||
eraseRecAppSyntaxExpr value
|
||||
/- `mkFix` invokes `decreasing_tactic` which may add auxiliary theorems to the environment. -/
|
||||
let value ← unfoldDeclsFrom envNew value
|
||||
|
||||
@@ -9,6 +9,12 @@ import Lean.Elab.RecAppSyntax
|
||||
namespace Lean.Elab.WF
|
||||
open Meta
|
||||
|
||||
private def shouldBetaReduce (e : Expr) (recFnNames : Array Name) : Bool :=
|
||||
if e.isHeadBetaTarget then
|
||||
e.getAppFn.find? (fun e => recFnNames.any (e.isConstOf ·)) |>.isSome
|
||||
else
|
||||
false
|
||||
|
||||
/--
|
||||
Preprocesses the expessions to improve the effectiveness of `wfRecursion`.
|
||||
|
||||
@@ -25,13 +31,11 @@ remove `let_fun`-lambdas that contain explicit termination proofs.
|
||||
-/
|
||||
def preprocess (e : Expr) : CoreM Expr :=
|
||||
Core.transform e
|
||||
(post := fun e =>
|
||||
match e with
|
||||
| .app (.mdata m f) a =>
|
||||
(post := fun e => do
|
||||
if e.isApp && e.getAppFn.isMData then
|
||||
let .mdata m f := e.getAppFn | unreachable!
|
||||
if m.isRecApp then
|
||||
return .done (.mdata m (.app f a))
|
||||
else
|
||||
return .done e
|
||||
| _ => return .done e)
|
||||
return .done (.mdata m (f.beta e.getAppArgs))
|
||||
return .continue)
|
||||
|
||||
end Lean.Elab.WF
|
||||
|
||||
@@ -14,12 +14,6 @@ namespace Lean.Elab.WF
|
||||
open Meta
|
||||
open Term
|
||||
|
||||
private def getRefFromElems (elems : Array TerminationByElement) : Syntax := Id.run do
|
||||
for elem in elems do
|
||||
if !elem.implicit then
|
||||
return elem.ref
|
||||
return elems[0]!.ref
|
||||
|
||||
private partial def unpackMutual (preDefs : Array PreDefinition) (mvarId : MVarId) (fvarId : FVarId) : TermElabM (Array (FVarId × MVarId)) := do
|
||||
let rec go (i : Nat) (mvarId : MVarId) (fvarId : FVarId) (result : Array (FVarId × MVarId)) : TermElabM (Array (FVarId × MVarId)) := do
|
||||
if i < preDefs.size - 1 then
|
||||
@@ -29,15 +23,22 @@ private partial def unpackMutual (preDefs : Array PreDefinition) (mvarId : MVarI
|
||||
return result.push (fvarId, mvarId)
|
||||
go 0 mvarId fvarId #[]
|
||||
|
||||
private partial def unpackUnary (preDef : PreDefinition) (prefixSize : Nat) (mvarId : MVarId) (fvarId : FVarId) (element : TerminationByElement) : TermElabM MVarId := do
|
||||
private partial def unpackUnary (preDef : PreDefinition) (prefixSize : Nat) (mvarId : MVarId)
|
||||
(fvarId : FVarId) (extraParams : Nat) (element : TerminationBy) : TermElabM MVarId := do
|
||||
-- If elements.vars is ≤ extraParams, this is user-provided, and should be interpreted
|
||||
-- as left to right. Else it is provided by GuessLex, and may rename non-extra paramters as well.
|
||||
-- (Not pretty, but it works for now)
|
||||
let implicit_underscores :=
|
||||
if element.vars.size < extraParams then extraParams - element.vars.size else 0
|
||||
let varNames ← lambdaTelescope preDef.value fun xs _ => do
|
||||
let mut varNames ← xs.mapM fun x => x.fvarId!.getUserName
|
||||
if element.vars.size > varNames.size then
|
||||
throwErrorAt element.vars[varNames.size]! "too many variable names"
|
||||
for h : i in [:element.vars.size] do
|
||||
let varStx := element.vars[i]'h.2
|
||||
let varStx := element.vars[i]
|
||||
if let `($ident:ident) := varStx then
|
||||
varNames := varNames.set! (varNames.size - element.vars.size + i) ident.getId
|
||||
let j := varNames.size - implicit_underscores - element.vars.size + i
|
||||
varNames := varNames.set! j ident.getId
|
||||
return varNames
|
||||
let mut mvarId := mvarId
|
||||
for localDecl in (← Term.getMVarDecl mvarId).lctx, varName in varNames[:prefixSize] do
|
||||
@@ -54,19 +55,19 @@ private partial def unpackUnary (preDef : PreDefinition) (prefixSize : Nat) (mva
|
||||
go 0 mvarId fvarId
|
||||
|
||||
def elabWFRel (preDefs : Array PreDefinition) (unaryPreDefName : Name) (fixedPrefixSize : Nat)
|
||||
(argType : Expr) (wf : TerminationWF) (k : Expr → TermElabM α) : TermElabM α := do
|
||||
(argType : Expr) (extraParamss : Array Nat) (wf : TerminationWF) (k : Expr → TermElabM α) :
|
||||
TermElabM α := do
|
||||
let α := argType
|
||||
let u ← getLevel α
|
||||
let expectedType := mkApp (mkConst ``WellFoundedRelation [u]) α
|
||||
trace[Elab.definition.wf] "elabWFRel start: {(← mkFreshTypeMVar).mvarId!}"
|
||||
withDeclName unaryPreDefName do
|
||||
withRef (getRefFromElems wf) do
|
||||
let mainMVarId := (← mkFreshExprSyntheticOpaqueMVar expectedType).mvarId!
|
||||
let [fMVarId, wfRelMVarId, _] ← mainMVarId.apply (← mkConstWithFreshMVarLevels ``invImage) | throwError "failed to apply 'invImage'"
|
||||
let (d, fMVarId) ← fMVarId.intro1
|
||||
let subgoals ← unpackMutual preDefs fMVarId d
|
||||
for (d, mvarId) in subgoals, element in wf, preDef in preDefs do
|
||||
let mvarId ← unpackUnary preDef fixedPrefixSize mvarId d element
|
||||
for (d, mvarId) in subgoals, extraParams in extraParamss, element in wf, preDef in preDefs do
|
||||
let mvarId ← unpackUnary preDef fixedPrefixSize mvarId d extraParams element
|
||||
mvarId.withContext do
|
||||
let value ← Term.withSynthesize <| elabTermEnsuringType element.body (← mvarId.getType)
|
||||
mvarId.assign value
|
||||
|
||||
@@ -1,213 +1,110 @@
|
||||
/-
|
||||
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
Authors: Leonardo de Moura, Joachim Breitner
|
||||
-/
|
||||
import Lean.Parser.Command
|
||||
import Lean.Parser.Term
|
||||
|
||||
set_option autoImplicit false
|
||||
|
||||
namespace Lean.Elab.WF
|
||||
|
||||
/-! # Support for `decreasing_by` -/
|
||||
|
||||
structure DecreasingByTactic where
|
||||
ref : Syntax
|
||||
value : Lean.TSyntax `Lean.Parser.Tactic.tacticSeq
|
||||
deriving Inhabited
|
||||
|
||||
inductive DecreasingBy where
|
||||
| none
|
||||
| one (val : DecreasingByTactic)
|
||||
| many (map : NameMap DecreasingByTactic)
|
||||
deriving Inhabited
|
||||
|
||||
open Parser.Command in
|
||||
/--
|
||||
This function takes a user-specified `decreasing_by` clause (as `Sytnax`).
|
||||
If it is a `decreasingByMany` (a set of clauses guarded by the function name) then
|
||||
* checks that all mentioned names exist in the current declaration
|
||||
* check that at most one function from each clique is mentioned
|
||||
and sort the entries by function name.
|
||||
-/
|
||||
def expandDecreasingBy? (decreasingBy? : Option Syntax) (cliques : Array (Array Name)) : MacroM DecreasingBy := do
|
||||
let some decreasingBy := decreasingBy? | return DecreasingBy.none
|
||||
let ref := decreasingBy
|
||||
match decreasingBy with
|
||||
| `(decreasingBy|decreasing_by $hint1:tacticSeq) =>
|
||||
return DecreasingBy.one { ref, value := hint1 }
|
||||
| `(decreasingBy|decreasing_by $hints:decreasingByMany) => do
|
||||
let m ← hints.raw[0].getArgs.foldlM (init := {}) fun m arg => do
|
||||
let arg : TSyntax `decreasingByElement := ⟨arg⟩ -- cannot use syntax pattern match with lookahead
|
||||
let `(decreasingByElement| $declId:ident => $tac:tacticSeq) := arg | Macro.throwUnsupported
|
||||
let declName? := cliques.findSome? fun clique => clique.findSome? fun declName =>
|
||||
if declId.getId.isSuffixOf declName then some declName else none
|
||||
match declName? with
|
||||
| none => Macro.throwErrorAt declId s!"function '{declId.getId}' not found in current declaration"
|
||||
| some declName => return m.insert declName { ref := arg, value := tac }
|
||||
for clique in cliques do
|
||||
let mut found? := Option.none
|
||||
for declName in clique do
|
||||
if let some { ref, .. } := m.find? declName then
|
||||
if let some found := found? then
|
||||
Macro.throwErrorAt ref s!"invalid termination hint element, '{declName}' and '{found}' are in the same clique"
|
||||
found? := some declName
|
||||
return DecreasingBy.many m
|
||||
| _ => Macro.throwUnsupported
|
||||
|
||||
def DecreasingBy.markAsUsed (t : DecreasingBy) (clique : Array Name) : DecreasingBy :=
|
||||
match t with
|
||||
| DecreasingBy.none => DecreasingBy.none
|
||||
| DecreasingBy.one .. => DecreasingBy.none
|
||||
| DecreasingBy.many m => Id.run do
|
||||
for declName in clique do
|
||||
if m.contains declName then
|
||||
let m := m.erase declName
|
||||
if m.isEmpty then
|
||||
return DecreasingBy.none
|
||||
else
|
||||
return DecreasingBy.many m
|
||||
return t
|
||||
|
||||
def DecreasingBy.find? (t : DecreasingBy) (clique : Array Name) : Option DecreasingByTactic :=
|
||||
match t with
|
||||
| DecreasingBy.none => Option.none
|
||||
| DecreasingBy.one v => some v
|
||||
| DecreasingBy.many m => clique.findSome? m.find?
|
||||
|
||||
def DecreasingBy.ensureAllUsed (t : DecreasingBy) : MacroM Unit := do
|
||||
match t with
|
||||
| DecreasingBy.one v => Macro.throwErrorAt v.ref "unused termination hint element"
|
||||
| DecreasingBy.many m => m.forM fun _ v => Macro.throwErrorAt v.ref "unused termination hint element"
|
||||
| _ => pure ()
|
||||
|
||||
/-! # Support for `termination_by` notation -/
|
||||
|
||||
/-- A single `termination_by` clause -/
|
||||
structure TerminationByElement where
|
||||
structure TerminationBy where
|
||||
ref : Syntax
|
||||
declName : Name
|
||||
vars : TSyntaxArray [`ident, ``Lean.Parser.Term.hole]
|
||||
body : Term
|
||||
implicit : Bool
|
||||
deriving Inhabited
|
||||
|
||||
/-- `termination_by` clauses, grouped by clique -/
|
||||
structure TerminationByClique where
|
||||
elements : Array TerminationByElement
|
||||
used : Bool := false
|
||||
open Parser.Termination in
|
||||
def TerminationBy.unexpand (wf : TerminationBy) : MetaM Syntax := do
|
||||
-- TODO: Why can I not just use $wf.vars in the quotation below?
|
||||
let vars : TSyntaxArray `ident := wf.vars.map (⟨·.raw⟩)
|
||||
if vars.isEmpty then
|
||||
`(terminationBy|termination_by $wf.body)
|
||||
else
|
||||
`(terminationBy|termination_by $vars* => $wf.body)
|
||||
|
||||
/--
|
||||
A `termination_by` hint, as specified by the user
|
||||
-/
|
||||
structure TerminationBy where
|
||||
cliques : Array TerminationByClique
|
||||
/-- A complete set of `termination_by` hints, as applicable to a single clique. -/
|
||||
abbrev TerminationWF := Array TerminationBy
|
||||
|
||||
/-- A single `decreasing_by` clause -/
|
||||
structure DecreasingBy where
|
||||
ref : Syntax
|
||||
tactic : TSyntax ``Lean.Parser.Tactic.tacticSeq
|
||||
deriving Inhabited
|
||||
|
||||
/-- The termination annotations for a single function.
|
||||
For `decreasing_by`, we store the whole `decreasing_by tacticSeq` expression, as this
|
||||
is what `Term.runTactic` expects.
|
||||
-/
|
||||
structure TerminationHints where
|
||||
ref : Syntax
|
||||
termination_by? : Option TerminationBy
|
||||
decreasing_by? : Option DecreasingBy
|
||||
/-- Here we record the number of parameters past the `:`. This is
|
||||
* `GuessLex` when there is no `termination_by` annotation, so that
|
||||
we can print the guessed order in the right form.
|
||||
* If there are fewer variables in the `termination_by` annotation than there are extra
|
||||
parameters, we know which parameters they should apply to.
|
||||
|
||||
It it set in `TerminationHints.checkVars`, which is the place where we also check that the user
|
||||
does not bind more extra parameters than present in the predefinition.
|
||||
-/
|
||||
extraParams : Nat
|
||||
deriving Inhabited
|
||||
|
||||
def TerminationHints.none : TerminationHints := ⟨.missing, .none, .none, 0⟩
|
||||
|
||||
/-- Logs warnings when the `TerminationHints` are present. -/
|
||||
def TerminationHints.ensureNone (hints : TerminationHints) (reason : String): CoreM Unit := do
|
||||
match hints.termination_by?, hints.decreasing_by? with
|
||||
| .none, .none => pure ()
|
||||
| .none, .some dec_by =>
|
||||
logErrorAt dec_by.ref m!"unused `decreasing_by`, function is {reason}"
|
||||
| .some term_by, .none =>
|
||||
logErrorAt term_by.ref m!"unused `termination_by`, function is {reason}"
|
||||
| .some _, .some _ =>
|
||||
logErrorAt hints.ref m!"unused termination hints, function is {reason}"
|
||||
|
||||
/--
|
||||
A `termination_by` hint, as applicable to a single clique
|
||||
Checks that `termination_by` binds at most as many variables are present in the outermost
|
||||
lambda of `value`, and logs (without failing) appropriate errors.
|
||||
|
||||
Also remembers `extraParams` for later use.
|
||||
-/
|
||||
abbrev TerminationWF := Array TerminationByElement
|
||||
def TerminationHints.checkVars (headerParams : Nat) (hints : TerminationHints) (value : Expr) :
|
||||
MetaM TerminationHints := do
|
||||
let extraParams := value.getNumHeadLambdas - headerParams
|
||||
if let .some tb := hints.termination_by? then
|
||||
if tb.vars.size > extraParams then
|
||||
logErrorAt tb.ref <| m!"Too many extra parameters bound; the function definition only " ++
|
||||
m!"has {extraParams} extra parameters."
|
||||
return { hints with extraParams := extraParams }
|
||||
|
||||
open Parser.Command in
|
||||
/--
|
||||
Expands the syntax for a `termination_by` clause, checking that
|
||||
* each function is mentioned once
|
||||
* each function mentioned actually occurs in the current declaration
|
||||
* if anything is specified, then all functions have a clause
|
||||
* the else-case (`_`) occurs only once, and only when needed
|
||||
open Parser.Termination
|
||||
|
||||
NB:
|
||||
```
|
||||
def terminationByElement := leading_parser ppLine >> (ident <|> hole) >> many (ident <|> hole) >> " => " >> termParser >> optional ";"
|
||||
def terminationBy := leading_parser ppLine >> "termination_by " >> many1chIndent terminationByElement
|
||||
```
|
||||
-/
|
||||
def expandTerminationBy? (hint? : Option Syntax) (cliques : Array (Array Name)) :
|
||||
MacroM TerminationBy := do
|
||||
let some hint := hint? | return { cliques := #[] }
|
||||
let `(terminationBy|termination_by $elementStxs*) := hint | Macro.throwUnsupported
|
||||
let mut alreadyFound : NameSet := {}
|
||||
let mut elseElemStx? := none
|
||||
for elementStx in elementStxs do
|
||||
match elementStx with
|
||||
| `(terminationByElement|$ident:ident $_* => $_) =>
|
||||
let declSuffix := ident.getId
|
||||
if alreadyFound.contains declSuffix then
|
||||
withRef elementStx <| Macro.throwError s!"invalid `termination_by` syntax, `{declSuffix}` case has already been provided"
|
||||
alreadyFound := alreadyFound.insert declSuffix
|
||||
if cliques.all fun clique => clique.all fun declName => !declSuffix.isSuffixOf declName then
|
||||
withRef elementStx <| Macro.throwError s!"function '{declSuffix}' not found in current declaration"
|
||||
| `(terminationByElement|_ $_vars* => $_term) =>
|
||||
if elseElemStx?.isSome then
|
||||
withRef elementStx <| Macro.throwError "invalid `termination_by` syntax, the else-case (i.e., `_ ... => ...`) has already been specified"
|
||||
else
|
||||
elseElemStx? := some elementStx
|
||||
| _ => Macro.throwUnsupported
|
||||
let toElement (declName : Name) (elementStx : TSyntax ``terminationByElement) : TerminationByElement :=
|
||||
match elementStx with
|
||||
| `(terminationByElement|$ioh $vars* => $body:term) =>
|
||||
let implicit := !ioh.raw.isIdent
|
||||
{ ref := elementStx, declName, vars, implicit, body }
|
||||
| _ => unreachable!
|
||||
let elementAppliesTo (declName : Name) : TSyntax ``terminationByElement → Bool
|
||||
| `(terminationByElement|$ident:ident $_* => $_) => ident.getId.isSuffixOf declName
|
||||
| _ => false
|
||||
let mut result := #[]
|
||||
let mut usedElse := false
|
||||
for clique in cliques do
|
||||
let mut elements := #[]
|
||||
for declName in clique do
|
||||
if let some elementStx := elementStxs.find? (elementAppliesTo declName) then
|
||||
elements := elements.push (toElement declName elementStx)
|
||||
else if let some elseElemStx := elseElemStx? then
|
||||
elements := elements.push (toElement declName elseElemStx)
|
||||
usedElse := true
|
||||
unless elements.isEmpty do
|
||||
if let some missing := clique.find? fun declName => elements.find? (·.declName == declName) |>.isNone then
|
||||
withRef elements[0]!.ref <| Macro.throwError s!"invalid `termination_by` syntax, missing case for function '{missing}'"
|
||||
result := result.push { elements }
|
||||
if !usedElse && elseElemStx?.isSome then
|
||||
withRef elseElemStx?.get! <| Macro.throwError s!"invalid `termination_by` syntax, unnecessary else-case"
|
||||
return ⟨result⟩
|
||||
|
||||
open Parser.Command in
|
||||
def TerminationWF.unexpand (elements : TerminationWF) : MetaM Syntax := do
|
||||
let elementStxs ← elements.mapM fun element => do
|
||||
let fn : Ident := mkIdent (← unresolveNameGlobal element.declName)
|
||||
`(terminationByElement|$fn $element.vars* => $element.body)
|
||||
`(terminationBy|termination_by $elementStxs*)
|
||||
|
||||
def TerminationBy.markAsUsed (t : TerminationBy) (cliqueNames : Array Name) : TerminationBy :=
|
||||
.mk <| t.cliques.map fun clique =>
|
||||
if cliqueNames.any fun name => clique.elements.any fun elem => elem.declName == name then
|
||||
{ clique with used := true }
|
||||
else
|
||||
clique
|
||||
|
||||
def TerminationBy.find? (t : TerminationBy) (cliqueNames : Array Name) : Option TerminationWF :=
|
||||
t.cliques.findSome? fun clique =>
|
||||
if cliqueNames.any fun name => clique.elements.any fun elem => elem.declName == name then
|
||||
some <| clique.elements
|
||||
else
|
||||
none
|
||||
|
||||
def TerminationByClique.allImplicit (c : TerminationByClique) : Bool :=
|
||||
c.elements.all fun elem => elem.implicit
|
||||
|
||||
def TerminationByClique.getExplicitElement? (c : TerminationByClique) : Option TerminationByElement :=
|
||||
c.elements.find? (!·.implicit)
|
||||
|
||||
def TerminationBy.ensureAllUsed (t : TerminationBy) : MacroM Unit := do
|
||||
let hasUsedAllImplicit := t.cliques.any fun c => c.allImplicit && c.used
|
||||
let mut reportedAllImplicit := true
|
||||
for clique in t.cliques do
|
||||
unless clique.used do
|
||||
if let some explicitElem := clique.getExplicitElement? then
|
||||
Macro.throwErrorAt explicitElem.ref "unused termination hint element"
|
||||
else if !hasUsedAllImplicit then
|
||||
unless reportedAllImplicit do
|
||||
reportedAllImplicit := true
|
||||
Macro.throwErrorAt clique.elements[0]!.ref "unused termination hint element"
|
||||
/-- Takes apart a `Termination.suffix` syntax object -/
|
||||
def elabTerminationHints {m} [Monad m] [MonadError m] (stx : TSyntax ``suffix) : m TerminationHints := do
|
||||
-- Fail gracefully upon partial parses
|
||||
if let .missing := stx.raw then
|
||||
return { TerminationHints.none with ref := stx }
|
||||
match stx with
|
||||
| `(suffix| $[$t?:terminationBy]? $[$d?:decreasingBy]? ) => do
|
||||
let termination_by? ← t?.mapM fun t => match t with
|
||||
| `(terminationBy|termination_by $vars* => $body) =>
|
||||
if vars.isEmpty then
|
||||
throwErrorAt t "no extra parameters bounds, please omit the `=>`"
|
||||
else
|
||||
pure {ref := t, vars, body}
|
||||
| `(terminationBy|termination_by $body:term) => pure {ref := t, vars := #[], body}
|
||||
| _ => throwErrorAt t "unexpected `termination_by` syntax"
|
||||
let decreasing_by? ← d?.mapM fun d => match d with
|
||||
| `(decreasingBy|decreasing_by $tactic) => pure {ref := d, tactic}
|
||||
| _ => throwErrorAt d "unexpected `decreasing_by` syntax"
|
||||
return { ref := stx, termination_by?, decreasing_by?, extraParams := 0 }
|
||||
| _ => throwErrorAt stx s!"Unexpected Termination.suffix syntax: {stx} of kind {stx.raw.getKind}"
|
||||
|
||||
end Lean.Elab.WF
|
||||
|
||||
@@ -291,9 +291,10 @@ mutual
|
||||
|
||||
/--
|
||||
Try to synthesize a term `val` using the tactic code `tacticCode`, and then assign `mvarId := val`.
|
||||
|
||||
The `tacticCode` syntax comprises the whole `by ...` expression.
|
||||
-/
|
||||
partial def runTactic (mvarId : MVarId) (tacticCode : Syntax) : TermElabM Unit := withoutAutoBoundImplicit do
|
||||
/- Recall, `tacticCode` is the whole `by ...` expression. -/
|
||||
let code := tacticCode[1]
|
||||
instantiateMVarDeclMVars mvarId
|
||||
/-
|
||||
|
||||
@@ -13,6 +13,7 @@ import Lean.Elab.Tactic.Match
|
||||
import Lean.Elab.Tactic.Rewrite
|
||||
import Lean.Elab.Tactic.Location
|
||||
import Lean.Elab.Tactic.Simp
|
||||
import Lean.Elab.Tactic.Simproc
|
||||
import Lean.Elab.Tactic.BuiltinTactic
|
||||
import Lean.Elab.Tactic.Split
|
||||
import Lean.Elab.Tactic.Conv
|
||||
|
||||
@@ -17,9 +17,9 @@ def applySimpResult (result : Simp.Result) : TacticM Unit := do
|
||||
updateLhs result.expr (← result.getProof)
|
||||
|
||||
@[builtin_tactic Lean.Parser.Tactic.Conv.simp] def evalSimp : Tactic := fun stx => withMainContext do
|
||||
let { ctx, dischargeWrapper, .. } ← mkSimpContext stx (eraseLocal := false)
|
||||
let { ctx, simprocs, dischargeWrapper, .. } ← mkSimpContext stx (eraseLocal := false)
|
||||
let lhs ← getLhs
|
||||
let (result, _) ← dischargeWrapper.with fun d? => simp lhs ctx (discharge? := d?)
|
||||
let (result, _) ← dischargeWrapper.with fun d? => simp lhs ctx (simprocs := simprocs) (discharge? := d?)
|
||||
applySimpResult result
|
||||
|
||||
@[builtin_tactic Lean.Parser.Tactic.Conv.simpMatch] def evalSimpMatch : Tactic := fun _ => withMainContext do
|
||||
|
||||
@@ -95,6 +95,7 @@ structure Context where
|
||||
structure State where
|
||||
argPos : Nat := 0 -- current argument position
|
||||
targetPos : Nat := 0 -- current target at targetsStx
|
||||
motive : Option MVarId -- motive metavariable
|
||||
f : Expr
|
||||
fType : Expr
|
||||
alts : Array Alt := #[]
|
||||
@@ -117,6 +118,7 @@ private def getFType : M Expr := do
|
||||
|
||||
structure Result where
|
||||
elimApp : Expr
|
||||
motive : MVarId
|
||||
alts : Array Alt := #[]
|
||||
others : Array MVarId := #[]
|
||||
|
||||
@@ -134,12 +136,13 @@ partial def mkElimApp (elimInfo : ElimInfo) (targets : Array Expr) (tag : Name)
|
||||
let argPos := (← get).argPos
|
||||
if ctx.elimInfo.motivePos == argPos then
|
||||
let motive ← mkFreshExprMVar (← getArgExpectedType) MetavarKind.syntheticOpaque
|
||||
modify fun s => { s with motive := motive.mvarId! }
|
||||
addNewArg motive
|
||||
else if ctx.elimInfo.targetsPos.contains argPos then
|
||||
let s ← get
|
||||
let ctx ← read
|
||||
unless s.targetPos < ctx.targets.size do
|
||||
throwError "insufficient number of targets for '{elimInfo.name}'"
|
||||
throwError "insufficient number of targets for '{elimInfo.elimExpr}'"
|
||||
let target := ctx.targets[s.targetPos]!
|
||||
let expectedType ← getArgExpectedType
|
||||
let target ← withAssignableSyntheticOpaque <| Term.ensureHasType expectedType target
|
||||
@@ -166,9 +169,8 @@ partial def mkElimApp (elimInfo : ElimInfo) (targets : Array Expr) (tag : Name)
|
||||
loop
|
||||
| _ =>
|
||||
pure ()
|
||||
let f ← Term.mkConst elimInfo.name
|
||||
let fType ← inferType f
|
||||
let (_, s) ← (loop).run { elimInfo := elimInfo, targets := targets } |>.run { f := f, fType := fType }
|
||||
let (_, s) ← (loop).run { elimInfo := elimInfo, targets := targets }
|
||||
|>.run { f := elimInfo.elimExpr, fType := elimInfo.elimType, motive := none }
|
||||
let mut others := #[]
|
||||
for mvarId in s.insts do
|
||||
try
|
||||
@@ -179,7 +181,9 @@ partial def mkElimApp (elimInfo : ElimInfo) (targets : Array Expr) (tag : Name)
|
||||
mvarId.setKind .syntheticOpaque
|
||||
others := others.push mvarId
|
||||
let alts ← s.alts.filterM fun alt => return !(← alt.mvarId.isAssigned)
|
||||
return { elimApp := (← instantiateMVars s.f), alts, others := others }
|
||||
let some motive := s.motive |
|
||||
throwError "mkElimApp: motive not found"
|
||||
return { elimApp := (← instantiateMVars s.f), alts, others, motive }
|
||||
|
||||
/-- Given a goal `... targets ... |- C[targets]` associated with `mvarId`, assign
|
||||
`motiveArg := fun targets => C[targets]` -/
|
||||
@@ -499,11 +503,36 @@ def getInductiveValFromMajor (major : Expr) : TacticM InductiveVal :=
|
||||
(fun _ => Meta.throwTacticEx `induction mvarId m!"major premise type is not an inductive type {indentExpr majorType}")
|
||||
(fun val _ => pure val)
|
||||
|
||||
-- `optElimId` is of the form `("using" ident)?`
|
||||
/--
|
||||
Elaborates the term in the `using` clause. We want to allow parameters to be instantiated
|
||||
(e.g. `using foo (p := …)`), but preserve other paramters, like the motives, as parameters,
|
||||
without turning them into MVars. So this uses `abstractMVars` at the end. This is inspired by
|
||||
`Lean.Elab.Tactic.addSimpTheorem`.
|
||||
|
||||
It also elaborates without `heedElabAsElim` so that users can use constants that are marked
|
||||
`elabAsElim` in the `using` clause`.
|
||||
-/
|
||||
private def elabTermForElim (stx : Syntax) : TermElabM Expr := do
|
||||
-- Short-circuit elaborating plain identifiers
|
||||
if stx.isIdent then
|
||||
if let some e ← Term.resolveId? stx (withInfo := true) then
|
||||
return e
|
||||
Term.withoutErrToSorry <| Term.withoutHeedElabAsElim do
|
||||
let e ← Term.elabTerm stx none (implicitLambda := false)
|
||||
Term.synthesizeSyntheticMVars (mayPostpone := false) (ignoreStuckTC := true)
|
||||
let e ← instantiateMVars e
|
||||
let e := e.eta
|
||||
if e.hasMVar then
|
||||
let r ← abstractMVars (levels := false) e
|
||||
return r.expr
|
||||
else
|
||||
return e
|
||||
|
||||
-- `optElimId` is of the form `("using" term)?`
|
||||
private def getElimNameInfo (optElimId : Syntax) (targets : Array Expr) (induction : Bool): TacticM ElimInfo := do
|
||||
if optElimId.isNone then
|
||||
if let some elimInfo ← getCustomEliminator? targets then
|
||||
return elimInfo
|
||||
if let some elimName ← getCustomEliminator? targets then
|
||||
return ← getElimInfo elimName
|
||||
unless targets.size == 1 do
|
||||
throwError "eliminator must be provided when multiple targets are used (use 'using <eliminator-name>'), and no default eliminator has been registered using attribute `[eliminator]`"
|
||||
let indVal ← getInductiveValFromMajor targets[0]!
|
||||
@@ -514,12 +543,17 @@ private def getElimNameInfo (optElimId : Syntax) (targets : Array Expr) (inducti
|
||||
let elimName := if induction then mkRecName indVal.name else mkCasesOnName indVal.name
|
||||
getElimInfo elimName indVal.name
|
||||
else
|
||||
let elimId := optElimId[1]
|
||||
let elimName ← withRef elimId do resolveGlobalConstNoOverloadWithInfo elimId
|
||||
let elimTerm := optElimId[1]
|
||||
let elimExpr ← withRef elimTerm do elabTermForElim elimTerm
|
||||
-- not a precise check, but covers the common cases of T.recOn / T.casesOn
|
||||
-- as well as user defined T.myInductionOn to locate the constructors of T
|
||||
let baseName? := if ← isInductive elimName.getPrefix then some elimName.getPrefix else none
|
||||
withRef elimId <| getElimInfo elimName baseName?
|
||||
let baseName? ← do
|
||||
let some elimName := elimExpr.getAppFn.constName? | pure none
|
||||
if ← isInductive elimName.getPrefix then
|
||||
pure (some elimName.getPrefix)
|
||||
else
|
||||
pure none
|
||||
withRef elimTerm <| getElimExprInfo elimExpr baseName?
|
||||
|
||||
private def shouldGeneralizeTarget (e : Expr) : MetaM Bool := do
|
||||
if let .fvar fvarId .. := e then
|
||||
@@ -557,8 +591,7 @@ private def generalizeTargets (exprs : Array Expr) : TacticM (Array Expr) := do
|
||||
let result ← withRef stx[1] do -- use target position as reference
|
||||
ElimApp.mkElimApp elimInfo targets tag
|
||||
trace[Elab.induction] "elimApp: {result.elimApp}"
|
||||
let elimArgs := result.elimApp.getAppArgs
|
||||
ElimApp.setMotiveArg mvarId elimArgs[elimInfo.motivePos]!.mvarId! targetFVarIds
|
||||
ElimApp.setMotiveArg mvarId result.motive targetFVarIds
|
||||
let optPreTac := getOptPreTacOfOptInductionAlts optInductionAlts
|
||||
mvarId.assign result.elimApp
|
||||
ElimApp.evalAlts elimInfo result.alts optPreTac alts initInfo (numGeneralized := n) (toClear := targetFVarIds)
|
||||
@@ -571,6 +604,7 @@ where
|
||||
throwError "index in target's type is not a variable (consider using the `cases` tactic instead){indentExpr target}"
|
||||
if foundFVars.contains target.fvarId! then
|
||||
throwError "target (or one of its indices) occurs more than once{indentExpr target}"
|
||||
foundFVars := foundFVars.insert target.fvarId!
|
||||
|
||||
def elabCasesTargets (targets : Array Syntax) : TacticM (Array Expr × Array (Ident × FVarId)) :=
|
||||
withMainContext do
|
||||
|
||||
@@ -88,7 +88,7 @@ def elabSimpConfig (optConfig : Syntax) (kind : SimpKind) : TermElabM Meta.Simp.
|
||||
| .simpAll => return (← elabSimpConfigCtxCore optConfig).toConfig
|
||||
| .dsimp => return { (← elabDSimpConfigCore optConfig) with }
|
||||
|
||||
private def addDeclToUnfoldOrTheorem (thms : Meta.SimpTheorems) (id : Origin) (e : Expr) (post : Bool) (inv : Bool) (kind : SimpKind) : MetaM Meta.SimpTheorems := do
|
||||
private def addDeclToUnfoldOrTheorem (thms : SimpTheorems) (id : Origin) (e : Expr) (post : Bool) (inv : Bool) (kind : SimpKind) : MetaM SimpTheorems := do
|
||||
if e.isConst then
|
||||
let declName := e.constName!
|
||||
let info ← getConstInfo declName
|
||||
@@ -115,7 +115,7 @@ private def addDeclToUnfoldOrTheorem (thms : Meta.SimpTheorems) (id : Origin) (e
|
||||
else
|
||||
thms.add id #[] e (post := post) (inv := inv)
|
||||
|
||||
private def addSimpTheorem (thms : Meta.SimpTheorems) (id : Origin) (stx : Syntax) (post : Bool) (inv : Bool) : TermElabM Meta.SimpTheorems := do
|
||||
private def addSimpTheorem (thms : SimpTheorems) (id : Origin) (stx : Syntax) (post : Bool) (inv : Bool) : TermElabM SimpTheorems := do
|
||||
let (levelParams, proof) ← Term.withoutModifyingElabMetaStateWithInfo <| withRef stx <| Term.withoutErrToSorry do
|
||||
let e ← Term.elabTerm stx none
|
||||
Term.synthesizeSyntheticMVars (mayPostpone := false) (ignoreStuckTC := true)
|
||||
@@ -129,12 +129,14 @@ private def addSimpTheorem (thms : Meta.SimpTheorems) (id : Origin) (stx : Synta
|
||||
thms.add id levelParams proof (post := post) (inv := inv)
|
||||
|
||||
structure ElabSimpArgsResult where
|
||||
ctx : Simp.Context
|
||||
starArg : Bool := false
|
||||
ctx : Simp.Context
|
||||
simprocs : Simprocs
|
||||
starArg : Bool := false
|
||||
|
||||
inductive ResolveSimpIdResult where
|
||||
| none
|
||||
| expr (e : Expr)
|
||||
| simproc (declName : Name)
|
||||
| ext (ext : SimpExtension)
|
||||
|
||||
/--
|
||||
@@ -142,9 +144,9 @@ inductive ResolveSimpIdResult where
|
||||
If `eraseLocal == true`, then we consider local declarations when resolving names for erased theorems (`- id`),
|
||||
this option only makes sense for `simp_all` or `*` is used.
|
||||
-/
|
||||
def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (eraseLocal : Bool) (kind : SimpKind) : TacticM ElabSimpArgsResult := do
|
||||
def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (simprocs : Simprocs) (eraseLocal : Bool) (kind : SimpKind) : TacticM ElabSimpArgsResult := do
|
||||
if stx.isNone then
|
||||
return { ctx }
|
||||
return { ctx, simprocs }
|
||||
else
|
||||
/-
|
||||
syntax simpPre := "↓"
|
||||
@@ -156,6 +158,7 @@ def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (eraseLocal : Bool) (kind :
|
||||
withMainContext do
|
||||
let mut thmsArray := ctx.simpTheorems
|
||||
let mut thms := thmsArray[0]!
|
||||
let mut simprocs := simprocs
|
||||
let mut starArg := false
|
||||
for arg in stx[1].getSepArgs do
|
||||
if arg.getKind == ``Lean.Parser.Tactic.simpErase then
|
||||
@@ -165,7 +168,9 @@ def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (eraseLocal : Bool) (kind :
|
||||
thms := thms.eraseCore (.fvar fvar.fvarId!)
|
||||
else
|
||||
let declName ← resolveGlobalConstNoOverloadWithInfo arg[1]
|
||||
if ctx.config.autoUnfold then
|
||||
if (← Simp.isSimproc declName) then
|
||||
simprocs := simprocs.erase declName
|
||||
else if ctx.config.autoUnfold then
|
||||
thms := thms.eraseCore (.decl declName)
|
||||
else
|
||||
thms ← thms.erase (.decl declName)
|
||||
@@ -177,11 +182,12 @@ def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (eraseLocal : Bool) (kind :
|
||||
arg[0][0].getKind == ``Parser.Tactic.simpPost
|
||||
let inv := !arg[1].isNone
|
||||
let term := arg[2]
|
||||
|
||||
match (← resolveSimpIdTheorem? term) with
|
||||
| .expr e =>
|
||||
let name ← mkFreshId
|
||||
thms ← addDeclToUnfoldOrTheorem thms (.stx name arg) e post inv kind
|
||||
| .simproc declName =>
|
||||
simprocs ← simprocs.add declName post
|
||||
| .ext ext =>
|
||||
thmsArray := thmsArray.push (← ext.getTheorems)
|
||||
| .none =>
|
||||
@@ -191,8 +197,13 @@ def elabSimpArgs (stx : Syntax) (ctx : Simp.Context) (eraseLocal : Bool) (kind :
|
||||
starArg := true
|
||||
else
|
||||
throwUnsupportedSyntax
|
||||
return { ctx := { ctx with simpTheorems := thmsArray.set! 0 thms }, starArg }
|
||||
return { ctx := { ctx with simpTheorems := thmsArray.set! 0 thms }, simprocs, starArg }
|
||||
where
|
||||
isSimproc? (e : Expr) : MetaM (Option Name) := do
|
||||
let .const declName _ := e | return none
|
||||
unless (← Simp.isSimproc declName) do return none
|
||||
return some declName
|
||||
|
||||
resolveSimpIdTheorem? (simpArgTerm : Term) : TacticM ResolveSimpIdResult := do
|
||||
let resolveExt (n : Name) : TacticM ResolveSimpIdResult := do
|
||||
if let some ext ← getSimpExtension? n then
|
||||
@@ -203,9 +214,16 @@ where
|
||||
| `($id:ident) =>
|
||||
try
|
||||
if let some e ← Term.resolveId? simpArgTerm (withInfo := true) then
|
||||
return .expr e
|
||||
if let some simprocDeclName ← isSimproc? e then
|
||||
return .simproc simprocDeclName
|
||||
else
|
||||
return .expr e
|
||||
else
|
||||
resolveExt id.getId.eraseMacroScopes
|
||||
let name := id.getId.eraseMacroScopes
|
||||
if (← Simp.isBuiltinSimproc name) then
|
||||
return .simproc name
|
||||
else
|
||||
resolveExt name
|
||||
catch _ =>
|
||||
resolveExt id.getId.eraseMacroScopes
|
||||
| _ =>
|
||||
@@ -218,6 +236,7 @@ where
|
||||
|
||||
structure MkSimpContextResult where
|
||||
ctx : Simp.Context
|
||||
simprocs : Simprocs
|
||||
dischargeWrapper : Simp.DischargeWrapper
|
||||
|
||||
/--
|
||||
@@ -238,8 +257,9 @@ def mkSimpContext (stx : Syntax) (eraseLocal : Bool) (kind := SimpKind.simp) (ig
|
||||
simpOnlyBuiltins.foldlM (·.addConst ·) ({} : SimpTheorems)
|
||||
else
|
||||
getSimpTheorems
|
||||
let simprocs ← if simpOnly then pure {} else Simp.getSimprocs
|
||||
let congrTheorems ← getSimpCongrTheorems
|
||||
let r ← elabSimpArgs stx[4] (eraseLocal := eraseLocal) (kind := kind) {
|
||||
let r ← elabSimpArgs stx[4] (eraseLocal := eraseLocal) (kind := kind) (simprocs := simprocs) {
|
||||
config := (← elabSimpConfig stx[1] (kind := kind))
|
||||
simpTheorems := #[simpTheorems], congrTheorems
|
||||
}
|
||||
@@ -247,6 +267,7 @@ def mkSimpContext (stx : Syntax) (eraseLocal : Bool) (kind := SimpKind.simp) (ig
|
||||
return { r with dischargeWrapper }
|
||||
else
|
||||
let ctx := r.ctx
|
||||
let simprocs := r.simprocs
|
||||
let mut simpTheorems := ctx.simpTheorems
|
||||
/-
|
||||
When using `zeta := false`, we do not expand let-declarations when using `[*]`.
|
||||
@@ -257,7 +278,7 @@ def mkSimpContext (stx : Syntax) (eraseLocal : Bool) (kind := SimpKind.simp) (ig
|
||||
unless simpTheorems.isErased (.fvar h) do
|
||||
simpTheorems ← simpTheorems.addTheorem (.fvar h) (← h.getDecl).toExpr
|
||||
let ctx := { ctx with simpTheorems }
|
||||
return { ctx, dischargeWrapper }
|
||||
return { ctx, simprocs, dischargeWrapper }
|
||||
|
||||
register_builtin_option tactic.simp.trace : Bool := {
|
||||
defValue := false
|
||||
@@ -281,12 +302,21 @@ def mkSimpOnly (stx : Syntax) (usedSimps : UsedSimps) : MetaM Syntax := do
|
||||
let env ← getEnv
|
||||
for (thm, _) in usedSimps.toArray.qsort (·.2 < ·.2) do
|
||||
match thm with
|
||||
| .decl declName inv => -- global definitions in the environment
|
||||
| .decl declName post inv => -- global definitions in the environment
|
||||
if env.contains declName && (inv || !simpOnlyBuiltins.contains declName) then
|
||||
args := args.push (if inv then
|
||||
(← `(Parser.Tactic.simpLemma| ← $(mkIdent (← unresolveNameGlobal declName)):ident))
|
||||
else
|
||||
(← `(Parser.Tactic.simpLemma| $(mkIdent (← unresolveNameGlobal declName)):ident)))
|
||||
let decl : Term ← `($(mkIdent (← unresolveNameGlobal declName)):ident)
|
||||
let arg ← match post, inv with
|
||||
| true, true => `(Parser.Tactic.simpLemma| ← $decl:term)
|
||||
| true, false => `(Parser.Tactic.simpLemma| $decl:term)
|
||||
| false, true => `(Parser.Tactic.simpLemma| ↓ ← $decl:term)
|
||||
| false, false => `(Parser.Tactic.simpLemma| ↓ $decl:term)
|
||||
args := args.push arg
|
||||
else if (← Simp.isBuiltinSimproc declName) then
|
||||
let decl := mkIdent declName
|
||||
let arg ← match post with
|
||||
| true => `(Parser.Tactic.simpLemma| $decl:term)
|
||||
| false => `(Parser.Tactic.simpLemma| ↓ $decl:term)
|
||||
args := args.push arg
|
||||
| .fvar fvarId => -- local hypotheses in the context
|
||||
-- `simp_all` always uses all propositional hypotheses (and it can't use
|
||||
-- any others). So `simp_all only [h]`, where `h` is a hypothesis, would
|
||||
@@ -331,7 +361,7 @@ For many tactics other than the simplifier,
|
||||
one should use the `withLocation` tactic combinator
|
||||
when working with a `location`.
|
||||
-/
|
||||
def simpLocation (ctx : Simp.Context) (discharge? : Option Simp.Discharge := none) (loc : Location) : TacticM UsedSimps := do
|
||||
def simpLocation (ctx : Simp.Context) (simprocs : Simprocs) (discharge? : Option Simp.Discharge := none) (loc : Location) : TacticM UsedSimps := do
|
||||
match loc with
|
||||
| Location.targets hyps simplifyTarget =>
|
||||
withMainContext do
|
||||
@@ -343,7 +373,7 @@ def simpLocation (ctx : Simp.Context) (discharge? : Option Simp.Discharge := non
|
||||
where
|
||||
go (fvarIdsToSimp : Array FVarId) (simplifyTarget : Bool) : TacticM UsedSimps := do
|
||||
let mvarId ← getMainGoal
|
||||
let (result?, usedSimps) ← simpGoal mvarId ctx (simplifyTarget := simplifyTarget) (discharge? := discharge?) (fvarIdsToSimp := fvarIdsToSimp)
|
||||
let (result?, usedSimps) ← simpGoal mvarId ctx (simprocs := simprocs) (simplifyTarget := simplifyTarget) (discharge? := discharge?) (fvarIdsToSimp := fvarIdsToSimp)
|
||||
match result? with
|
||||
| none => replaceMainGoal []
|
||||
| some (_, mvarId) => replaceMainGoal [mvarId]
|
||||
@@ -353,15 +383,15 @@ where
|
||||
"simp " (config)? (discharger)? ("only ")? ("[" simpLemma,* "]")? (location)?
|
||||
-/
|
||||
@[builtin_tactic Lean.Parser.Tactic.simp] def evalSimp : Tactic := fun stx => withMainContext do
|
||||
let { ctx, dischargeWrapper } ← mkSimpContext stx (eraseLocal := false)
|
||||
let { ctx, simprocs, dischargeWrapper } ← mkSimpContext stx (eraseLocal := false)
|
||||
let usedSimps ← dischargeWrapper.with fun discharge? =>
|
||||
simpLocation ctx discharge? (expandOptLocation stx[5])
|
||||
simpLocation ctx simprocs discharge? (expandOptLocation stx[5])
|
||||
if tactic.simp.trace.get (← getOptions) then
|
||||
traceSimpCall stx usedSimps
|
||||
|
||||
@[builtin_tactic Lean.Parser.Tactic.simpAll] def evalSimpAll : Tactic := fun stx => withMainContext do
|
||||
let { ctx, .. } ← mkSimpContext stx (eraseLocal := true) (kind := .simpAll) (ignoreStarArg := true)
|
||||
let (result?, usedSimps) ← simpAll (← getMainGoal) ctx
|
||||
let { ctx, simprocs, .. } ← mkSimpContext stx (eraseLocal := true) (kind := .simpAll) (ignoreStarArg := true)
|
||||
let (result?, usedSimps) ← simpAll (← getMainGoal) ctx (simprocs := simprocs)
|
||||
match result? with
|
||||
| none => replaceMainGoal []
|
||||
| some mvarId => replaceMainGoal [mvarId]
|
||||
|
||||
85
src/Lean/Elab/Tactic/Simproc.lean
Normal file
85
src/Lean/Elab/Tactic/Simproc.lean
Normal file
@@ -0,0 +1,85 @@
|
||||
/-
|
||||
Copyright (c) 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
import Lean.Meta.Tactic.Simp.Simproc
|
||||
import Lean.Elab.Binders
|
||||
import Lean.Elab.SyntheticMVars
|
||||
import Lean.Elab.Term
|
||||
import Lean.Elab.Command
|
||||
|
||||
namespace Lean.Elab
|
||||
|
||||
open Lean Meta Simp
|
||||
|
||||
def elabSimprocPattern (stx : Syntax) : MetaM Expr := do
|
||||
let go : TermElabM Expr := do
|
||||
let pattern ← Term.elabTerm stx none
|
||||
Term.synthesizeSyntheticMVars
|
||||
return pattern
|
||||
go.run'
|
||||
|
||||
def elabSimprocKeys (stx : Syntax) : MetaM (Array Meta.SimpTheoremKey) := do
|
||||
let pattern ← elabSimprocPattern stx
|
||||
DiscrTree.mkPath pattern simpDtConfig
|
||||
|
||||
def checkSimprocType (declName : Name) : CoreM Unit := do
|
||||
let decl ← getConstInfo declName
|
||||
match decl.type with
|
||||
| .const ``Simproc _ => pure ()
|
||||
| _ => throwError "unexpected type at '{declName}', 'Simproc' expected"
|
||||
|
||||
namespace Command
|
||||
|
||||
@[builtin_command_elab Lean.Parser.simprocPattern] def elabSimprocPattern : CommandElab := fun stx => do
|
||||
let `(simproc_pattern% $pattern => $declName) := stx | throwUnsupportedSyntax
|
||||
let declName ← resolveGlobalConstNoOverload declName
|
||||
liftTermElabM do
|
||||
checkSimprocType declName
|
||||
let keys ← elabSimprocKeys pattern
|
||||
registerSimproc declName keys
|
||||
|
||||
@[builtin_command_elab Lean.Parser.simprocPatternBuiltin] def elabSimprocPatternBuiltin : CommandElab := fun stx => do
|
||||
let `(builtin_simproc_pattern% $pattern => $declName) := stx | throwUnsupportedSyntax
|
||||
let declName ← resolveGlobalConstNoOverload declName
|
||||
liftTermElabM do
|
||||
checkSimprocType declName
|
||||
let keys ← elabSimprocKeys pattern
|
||||
let val := mkAppN (mkConst ``registerBuiltinSimproc) #[toExpr declName, toExpr keys, mkConst declName]
|
||||
let initDeclName ← mkFreshUserName (declName ++ `declare)
|
||||
declareBuiltin initDeclName val
|
||||
|
||||
end Command
|
||||
|
||||
builtin_initialize
|
||||
registerBuiltinAttribute {
|
||||
ref := by exact decl_name%
|
||||
name := `simprocAttr
|
||||
descr := "Simplification procedure"
|
||||
erase := eraseSimprocAttr
|
||||
add := fun declName stx attrKind => do
|
||||
let go : MetaM Unit := do
|
||||
let post := if stx[1].isNone then true else stx[1][0].getKind == ``Lean.Parser.Tactic.simpPost
|
||||
addSimprocAttr declName attrKind post
|
||||
go.run' {}
|
||||
applicationTime := AttributeApplicationTime.afterCompilation
|
||||
}
|
||||
|
||||
builtin_initialize
|
||||
registerBuiltinAttribute {
|
||||
ref := by exact decl_name%
|
||||
name := `simprocBuiltinAttr
|
||||
descr := "Builtin simplification procedure"
|
||||
erase := eraseSimprocAttr
|
||||
add := fun declName stx _ => do
|
||||
let go : MetaM Unit := do
|
||||
let post := if stx[1].isNone then true else stx[1][0].getKind == ``Lean.Parser.Tactic.simpPost
|
||||
let val := mkAppN (mkConst ``addSimprocBuiltinAttr) #[toExpr declName, toExpr post, mkConst declName]
|
||||
let initDeclName ← mkFreshUserName (declName ++ `declare)
|
||||
declareBuiltin initDeclName val
|
||||
go.run' {}
|
||||
applicationTime := AttributeApplicationTime.afterCompilation
|
||||
}
|
||||
|
||||
end Lean.Elab
|
||||
@@ -10,6 +10,7 @@ import Lean.Linter.Deprecated
|
||||
import Lean.Elab.Config
|
||||
import Lean.Elab.Level
|
||||
import Lean.Elab.DeclModifiers
|
||||
import Lean.Elab.PreDefinition.WF.TerminationHint
|
||||
|
||||
namespace Lean.Elab
|
||||
|
||||
@@ -95,6 +96,7 @@ structure LetRecToLift where
|
||||
type : Expr
|
||||
val : Expr
|
||||
mvarId : MVarId
|
||||
termination : WF.TerminationHints
|
||||
deriving Inhabited
|
||||
|
||||
/--
|
||||
@@ -196,6 +198,8 @@ structure Context where
|
||||
sectionFVars : NameMap Expr := {}
|
||||
/-- Enable/disable implicit lambdas feature. -/
|
||||
implicitLambda : Bool := true
|
||||
/-- Heed `elab_as_elim` attribute. -/
|
||||
heedElabAsElim : Bool := true
|
||||
/-- Noncomputable sections automatically add the `noncomputable` modifier to any declaration we cannot generate code for. -/
|
||||
isNoncomputableSection : Bool := false
|
||||
/-- When `true` we skip TC failures. We use this option when processing patterns. -/
|
||||
@@ -325,33 +329,6 @@ instance : AddErrorMessageContext TermElabM where
|
||||
let msg ← addMacroStack msg ctx.macroStack
|
||||
pure (ref, msg)
|
||||
|
||||
/--
|
||||
Execute `x` but discard changes performed at `Term.State` and `Meta.State`.
|
||||
Recall that the `Environment` and `InfoState` are at `Core.State`. Thus, any updates to it will
|
||||
be preserved. This method is useful for performing computations where all
|
||||
metavariable must be resolved or discarded.
|
||||
The `InfoTree`s are not discarded, however, and wrapped in `InfoTree.Context`
|
||||
to store their metavariable context. -/
|
||||
def withoutModifyingElabMetaStateWithInfo (x : TermElabM α) : TermElabM α := do
|
||||
let s ← get
|
||||
let sMeta ← getThe Meta.State
|
||||
try
|
||||
withSaveInfoContext x
|
||||
finally
|
||||
set s
|
||||
set sMeta
|
||||
|
||||
/--
|
||||
Execute `x` but discard changes performed to the state.
|
||||
However, the info trees and messages are not discarded. -/
|
||||
private def withoutModifyingStateWithInfoAndMessagesImpl (x : TermElabM α) : TermElabM α := do
|
||||
let saved ← saveState
|
||||
try
|
||||
withSaveInfoContext x
|
||||
finally
|
||||
let saved := { saved with meta.core.infoState := (← getInfoState), meta.core.messages := (← getThe Core.State).messages }
|
||||
restoreState saved
|
||||
|
||||
/--
|
||||
Execute `x` without storing `Syntax` for recursive applications. See `saveRecAppSyntax` field at `Context`.
|
||||
-/
|
||||
@@ -398,9 +375,12 @@ def getLetRecsToLift : TermElabM (List LetRecToLift) := return (← get).letRecs
|
||||
/-- Return the declaration of the given metavariable -/
|
||||
def getMVarDecl (mvarId : MVarId) : TermElabM MetavarDecl := return (← getMCtx).getDecl mvarId
|
||||
|
||||
/-- Execute `x` with `declName? := name`. See `getDeclName?`. -/
|
||||
instance : MonadParentDecl TermElabM where
|
||||
getParentDeclName? := getDeclName?
|
||||
|
||||
/-- Execute `withSaveParentDeclInfoContext x` with `declName? := name`. See `getDeclName?`. -/
|
||||
def withDeclName (name : Name) (x : TermElabM α) : TermElabM α :=
|
||||
withReader (fun ctx => { ctx with declName? := name }) x
|
||||
withReader (fun ctx => { ctx with declName? := name }) <| withSaveParentDeclInfoContext x
|
||||
|
||||
/-- Update the universe level parameter names. -/
|
||||
def setLevelNames (levelNames : List Name) : TermElabM Unit :=
|
||||
@@ -431,6 +411,44 @@ def withoutErrToSorryImp (x : TermElabM α) : TermElabM α :=
|
||||
def withoutErrToSorry [MonadFunctorT TermElabM m] : m α → m α :=
|
||||
monadMap (m := TermElabM) withoutErrToSorryImp
|
||||
|
||||
def withoutHeedElabAsElimImp (x : TermElabM α) : TermElabM α :=
|
||||
withReader (fun ctx => { ctx with heedElabAsElim := false }) x
|
||||
|
||||
/--
|
||||
Execute `x` without heeding the `elab_as_elim` attribute. Useful when there is
|
||||
no expected type (so `elabAppArgs` would fail), but expect that the user wants
|
||||
to use such constants.
|
||||
-/
|
||||
def withoutHeedElabAsElim [MonadFunctorT TermElabM m] : m α → m α :=
|
||||
monadMap (m := TermElabM) withoutHeedElabAsElimImp
|
||||
|
||||
/--
|
||||
Execute `x` but discard changes performed at `Term.State` and `Meta.State`.
|
||||
Recall that the `Environment` and `InfoState` are at `Core.State`. Thus, any updates to it will
|
||||
be preserved. This method is useful for performing computations where all
|
||||
metavariable must be resolved or discarded.
|
||||
The `InfoTree`s are not discarded, however, and wrapped in `InfoTree.Context`
|
||||
to store their metavariable context. -/
|
||||
def withoutModifyingElabMetaStateWithInfo (x : TermElabM α) : TermElabM α := do
|
||||
let s ← get
|
||||
let sMeta ← getThe Meta.State
|
||||
try
|
||||
withSaveInfoContext x
|
||||
finally
|
||||
set s
|
||||
set sMeta
|
||||
|
||||
/--
|
||||
Execute `x` but discard changes performed to the state.
|
||||
However, the info trees and messages are not discarded. -/
|
||||
private def withoutModifyingStateWithInfoAndMessagesImpl (x : TermElabM α) : TermElabM α := do
|
||||
let saved ← saveState
|
||||
try
|
||||
withSaveInfoContext x
|
||||
finally
|
||||
let saved := { saved with meta.core.infoState := (← getInfoState), meta.core.messages := (← getThe Core.State).messages }
|
||||
restoreState saved
|
||||
|
||||
/-- For testing `TermElabM` methods. The #eval command will sign the error. -/
|
||||
def throwErrorIfErrors : TermElabM Unit := do
|
||||
if (← MonadLog.hasErrors) then
|
||||
|
||||
@@ -655,7 +655,7 @@ def mkProj (structName : Name) (idx : Nat) (struct : Expr) : Expr :=
|
||||
/--
|
||||
`.app f a` is now the preferred form.
|
||||
-/
|
||||
def mkApp (f a : Expr) : Expr :=
|
||||
@[match_pattern] def mkApp (f a : Expr) : Expr :=
|
||||
.app f a
|
||||
|
||||
/--
|
||||
@@ -684,16 +684,16 @@ def mkSimpleThunk (type : Expr) : Expr :=
|
||||
def mkLet (x : Name) (t : Expr) (v : Expr) (b : Expr) (nonDep : Bool := false) : Expr :=
|
||||
.letE x t v b nonDep
|
||||
|
||||
def mkAppB (f a b : Expr) := mkApp (mkApp f a) b
|
||||
def mkApp2 (f a b : Expr) := mkAppB f a b
|
||||
def mkApp3 (f a b c : Expr) := mkApp (mkAppB f a b) c
|
||||
def mkApp4 (f a b c d : Expr) := mkAppB (mkAppB f a b) c d
|
||||
def mkApp5 (f a b c d e : Expr) := mkApp (mkApp4 f a b c d) e
|
||||
def mkApp6 (f a b c d e₁ e₂ : Expr) := mkAppB (mkApp4 f a b c d) e₁ e₂
|
||||
def mkApp7 (f a b c d e₁ e₂ e₃ : Expr) := mkApp3 (mkApp4 f a b c d) e₁ e₂ e₃
|
||||
def mkApp8 (f a b c d e₁ e₂ e₃ e₄ : Expr) := mkApp4 (mkApp4 f a b c d) e₁ e₂ e₃ e₄
|
||||
def mkApp9 (f a b c d e₁ e₂ e₃ e₄ e₅ : Expr) := mkApp5 (mkApp4 f a b c d) e₁ e₂ e₃ e₄ e₅
|
||||
def mkApp10 (f a b c d e₁ e₂ e₃ e₄ e₅ e₆ : Expr) := mkApp6 (mkApp4 f a b c d) e₁ e₂ e₃ e₄ e₅ e₆
|
||||
@[match_pattern] def mkAppB (f a b : Expr) := mkApp (mkApp f a) b
|
||||
@[match_pattern] def mkApp2 (f a b : Expr) := mkAppB f a b
|
||||
@[match_pattern] def mkApp3 (f a b c : Expr) := mkApp (mkAppB f a b) c
|
||||
@[match_pattern] def mkApp4 (f a b c d : Expr) := mkAppB (mkAppB f a b) c d
|
||||
@[match_pattern] def mkApp5 (f a b c d e : Expr) := mkApp (mkApp4 f a b c d) e
|
||||
@[match_pattern] def mkApp6 (f a b c d e₁ e₂ : Expr) := mkAppB (mkApp4 f a b c d) e₁ e₂
|
||||
@[match_pattern] def mkApp7 (f a b c d e₁ e₂ e₃ : Expr) := mkApp3 (mkApp4 f a b c d) e₁ e₂ e₃
|
||||
@[match_pattern] def mkApp8 (f a b c d e₁ e₂ e₃ e₄ : Expr) := mkApp4 (mkApp4 f a b c d) e₁ e₂ e₃ e₄
|
||||
@[match_pattern] def mkApp9 (f a b c d e₁ e₂ e₃ e₄ e₅ : Expr) := mkApp5 (mkApp4 f a b c d) e₁ e₂ e₃ e₄ e₅
|
||||
@[match_pattern] def mkApp10 (f a b c d e₁ e₂ e₃ e₄ e₅ e₆ : Expr) := mkApp6 (mkApp4 f a b c d) e₁ e₂ e₃ e₄ e₅ e₆
|
||||
|
||||
/--
|
||||
`.lit l` is now the preferred form.
|
||||
@@ -735,7 +735,9 @@ def mkStrLit (s : String) : Expr :=
|
||||
@[export lean_expr_mk_mdata] def mkMDataEx : MData → Expr → Expr := mkMData
|
||||
@[export lean_expr_mk_proj] def mkProjEx : Name → Nat → Expr → Expr := mkProj
|
||||
|
||||
/-- `mkAppN f #[a₀, ..., aₙ]` ==> `f a₀ a₁ .. aₙ`-/
|
||||
/--
|
||||
`mkAppN f #[a₀, ..., aₙ]` constructs the application `f a₀ a₁ ... aₙ`.
|
||||
-/
|
||||
def mkAppN (f : Expr) (args : Array Expr) : Expr :=
|
||||
args.foldl mkApp f
|
||||
|
||||
@@ -919,6 +921,17 @@ private def getAppNumArgsAux : Expr → Nat → Nat
|
||||
def getAppNumArgs (e : Expr) : Nat :=
|
||||
getAppNumArgsAux e 0
|
||||
|
||||
/--
|
||||
Like `Lean.Expr.getAppFn` but assumes the application has up to `maxArgs` arguments.
|
||||
If there are any more arguments than this, then they are returned by `getAppFn` as part of the function.
|
||||
|
||||
In particular, if the given expression is a sequence of function applications `f a₁ .. aₙ`,
|
||||
returns `f a₁ .. aₖ` where `k` is minimal such that `n - k ≤ maxArgs`.
|
||||
-/
|
||||
def getBoundedAppFn : (maxArgs : Nat) → Expr → Expr
|
||||
| maxArgs' + 1, .app f _ => getBoundedAppFn maxArgs' f
|
||||
| _, e => e
|
||||
|
||||
private def getAppArgsAux : Expr → Array Expr → Nat → Array Expr
|
||||
| app f a, as, i => getAppArgsAux f (as.set! i a) (i-1)
|
||||
| _, as, _ => as
|
||||
@@ -929,6 +942,21 @@ private def getAppArgsAux : Expr → Array Expr → Nat → Array Expr
|
||||
let nargs := e.getAppNumArgs
|
||||
getAppArgsAux e (mkArray nargs dummy) (nargs-1)
|
||||
|
||||
private def getBoundedAppArgsAux : Expr → Array Expr → Nat → Array Expr
|
||||
| app f a, as, i + 1 => getBoundedAppArgsAux f (as.set! i a) i
|
||||
| _, as, _ => as
|
||||
|
||||
/--
|
||||
Like `Lean.Expr.getAppArgs` but returns up to `maxArgs` arguments.
|
||||
|
||||
In particular, given `f a₁ a₂ ... aₙ`, returns `#[aₖ₊₁, ..., aₙ]`
|
||||
where `k` is minimal such that the size of this array is at most `maxArgs`.
|
||||
-/
|
||||
@[inline] def getBoundedAppArgs (maxArgs : Nat) (e : Expr) : Array Expr :=
|
||||
let dummy := mkSort levelZero
|
||||
let nargs := min maxArgs e.getAppNumArgs
|
||||
getBoundedAppArgsAux e (mkArray nargs dummy) nargs
|
||||
|
||||
private def getAppRevArgsAux : Expr → Array Expr → Array Expr
|
||||
| app f a, as => getAppRevArgsAux f (as.push a)
|
||||
| _, as => as
|
||||
@@ -947,6 +975,37 @@ private def getAppRevArgsAux : Expr → Array Expr → Array Expr
|
||||
let nargs := e.getAppNumArgs
|
||||
withAppAux k e (mkArray nargs dummy) (nargs-1)
|
||||
|
||||
/--
|
||||
Given `f a_1 ... a_n`, returns `#[a_1, ..., a_n]`.
|
||||
Note that `f` may be an application.
|
||||
The resulting array has size `n` even if `f.getAppNumArgs < n`.
|
||||
-/
|
||||
@[inline] def getAppArgsN (e : Expr) (n : Nat) : Array Expr :=
|
||||
let dummy := mkSort levelZero
|
||||
loop n e (mkArray n dummy)
|
||||
where
|
||||
loop : Nat → Expr → Array Expr → Array Expr
|
||||
| 0, _, as => as
|
||||
| i+1, .app f a, as => loop i f (as.set! i a)
|
||||
| _, _, _ => panic! "too few arguments at"
|
||||
|
||||
/--
|
||||
Given `e` of the form `f a_1 ... a_n`, return `f`.
|
||||
If `n` is greater than the number of arguments, then return `e.getAppFn`.
|
||||
-/
|
||||
def stripArgsN (e : Expr) (n : Nat) : Expr :=
|
||||
match n, e with
|
||||
| 0, _ => e
|
||||
| n+1, .app f _ => stripArgsN f n
|
||||
| _, _ => e
|
||||
|
||||
/--
|
||||
Given `e` of the form `f a_1 ... a_n ... a_m`, return `f a_1 ... a_n`.
|
||||
If `n` is greater than the arity, then return `e`.
|
||||
-/
|
||||
def getAppPrefix (e : Expr) (n : Nat) : Expr :=
|
||||
e.stripArgsN (e.getAppNumArgs - n)
|
||||
|
||||
/-- Given `e = fn a₁ ... aₙ`, runs `f` on `fn` and each of the arguments `aᵢ` and
|
||||
makes a new function application with the results. -/
|
||||
def traverseApp {M} [Monad M]
|
||||
@@ -1141,10 +1200,9 @@ def hasLooseBVarInExplicitDomain : Expr → Nat → Bool → Bool
|
||||
|
||||
/--
|
||||
Lower the loose bound variables `>= s` in `e` by `d`.
|
||||
That is, a loose bound variable `bvar i`.
|
||||
`i >= s` is mapped into `bvar (i-d)`.
|
||||
That is, a loose bound variable `bvar i` with `i >= s` is mapped to `bvar (i-d)`.
|
||||
|
||||
Remark: if `s < d`, then result is `e`
|
||||
Remark: if `s < d`, then the result is `e`.
|
||||
-/
|
||||
@[extern "lean_expr_lower_loose_bvars"]
|
||||
opaque lowerLooseBVars (e : @& Expr) (s d : @& Nat) : Expr
|
||||
@@ -1171,32 +1229,84 @@ def inferImplicit : Expr → Nat → Bool → Expr
|
||||
| e, _, _ => e
|
||||
|
||||
/--
|
||||
Instantiate the loose bound variables in `e` using `subst`.
|
||||
That is, a loose `Expr.bvar i` is replaced with `subst[i]`.
|
||||
Instantiates the loose bound variables in `e` using the `subst` array,
|
||||
where a loose `Expr.bvar i` at "binding depth" `d` is instantiated with `subst[i - d]` if `0 <= i - d < subst.size`,
|
||||
and otherwise it is replaced with `Expr.bvar (i - subst.size)`; non-loose bound variables are not touched.
|
||||
|
||||
If we imagine all expressions as being able to refer to the infinite list of loose bound variables ..., 3, 2, 1, 0 in that order,
|
||||
then conceptually `instantiate` is instantiating the last `n` of these and reindexing the remaining ones.
|
||||
Warning: `instantiate` uses the de Bruijn indexing to index the `subst` array, which might be the reverse order from what you might expect.
|
||||
See also `Lean.Expr.instantiateRev`.
|
||||
|
||||
**Terminology.** The "binding depth" of a subexpression is the number of bound variables available to that subexpression
|
||||
by virtue of being in the bodies of `Expr.forallE`, `Expr.lam`, and `Expr.letE` expressions.
|
||||
A bound variable `Expr.bvar i` is "loose" if its de Bruijn index `i` is not less than its binding depth.)
|
||||
|
||||
**About instantiation.** Instantiation isn't mere substitution.
|
||||
When an expression from `subst` is being instantiated, its internal loose bound variables have their de Bruijn indices incremented
|
||||
by the binding depth of the replaced loose bound variable.
|
||||
This is necessary for the substituted expression to still refer to the correct binders after instantiation.
|
||||
Similarly, the reason loose bound variables not instantiated using `subst` have their de Bruijn indices decremented like `Expr.bvar (i - subst.size)`
|
||||
is that `instantiate` can be used to eliminate binding expressions internal to a larger expression,
|
||||
and this adjustment keeps these bound variables referring to the same binders.
|
||||
-/
|
||||
@[extern "lean_expr_instantiate"]
|
||||
opaque instantiate (e : @& Expr) (subst : @& Array Expr) : Expr
|
||||
|
||||
/--
|
||||
Instantiates loose bound variable `0` in `e` using the expression `subst`,
|
||||
where in particular a loose `Expr.bvar i` at binding depth `d` is instantiated with `subst` if `i = d`,
|
||||
and otherwise it is replaced with `Expr.bvar (i - 1)`; non-loose bound variables are not touched.
|
||||
|
||||
If we imagine all expressions as being able to refer to the infinite list of loose bound variables ..., 3, 2, 1, 0 in that order,
|
||||
then conceptually `instantiate1` is instantiating the last one of these and reindexing the remaining ones.
|
||||
|
||||
This function is equivalent to `instantiate e #[subst]`, but it avoids allocating an array.
|
||||
|
||||
See the documentation for `Lean.Expr.instantiate` for a description of instantiation.
|
||||
In short, during instantiation the loose bound variables in `subst` have their own de Bruijn indices updated to account
|
||||
for the binding depth of the replaced loose bound variable.
|
||||
-/
|
||||
@[extern "lean_expr_instantiate1"]
|
||||
opaque instantiate1 (e : @& Expr) (subst : @& Expr) : Expr
|
||||
|
||||
/-- Similar to instantiate, but `Expr.bvar i` is replaced with `subst[subst.size - i - 1]` -/
|
||||
/--
|
||||
Instantiates the loose bound variables in `e` using the `subst` array.
|
||||
This is equivalent to `Lean.Expr.instantiate e subst.reverse`, but it avoids reversing the array.
|
||||
In particular, rather than instantiating `Expr.bvar i` with `subst[i - d]` it instantiates with `subst[subst.size - 1 - (i - d)]`,
|
||||
where `d` is the binding depth.
|
||||
|
||||
This function instantiates with the "forwards" indexing scheme.
|
||||
For example, if `e` represents the expression `fun x y => x + y`,
|
||||
then `instantiateRev e.bindingBody!.bindingBody! #[a, b]` yields `a + b`.
|
||||
The `instantiate` function on the other hand would yield `b + a`, since de Bruijn indices count outwards.
|
||||
-/
|
||||
@[extern "lean_expr_instantiate_rev"]
|
||||
opaque instantiateRev (e : @& Expr) (subst : @& Array Expr) : Expr
|
||||
|
||||
/--
|
||||
Similar to `instantiate`, but consider only the variables `xs` in the range `[beginIdx, endIdx)`.
|
||||
Function panics if `beginIdx <= endIdx <= xs.size` does not hold.
|
||||
Similar to `Lean.Expr.instantiate`, but considers only the substitutions `subst` in the range `[beginIdx, endIdx)`.
|
||||
Function panics if `beginIdx <= endIdx <= subst.size` does not hold.
|
||||
|
||||
This function is equivalent to `instantiate e (subst.extract beginIdx endIdx)`, but it does not allocate a new array.
|
||||
|
||||
This instantiates with the "backwards" indexing scheme.
|
||||
See also `Lean.Expr.instantiateRevRange`, which instantiates with the "forwards" indexing scheme.
|
||||
-/
|
||||
@[extern "lean_expr_instantiate_range"]
|
||||
opaque instantiateRange (e : @& Expr) (beginIdx endIdx : @& Nat) (xs : @& Array Expr) : Expr
|
||||
opaque instantiateRange (e : @& Expr) (beginIdx endIdx : @& Nat) (subst : @& Array Expr) : Expr
|
||||
|
||||
/--
|
||||
Similar to `instantiateRev`, but consider only the variables `xs` in the range `[beginIdx, endIdx)`.
|
||||
Function panics if `beginIdx <= endIdx <= xs.size` does not hold.
|
||||
Similar to `Lean.Expr.instantiateRev`, but considers only the substitutions `subst` in the range `[beginIdx, endIdx)`.
|
||||
Function panics if `beginIdx <= endIdx <= subst.size` does not hold.
|
||||
|
||||
This function is equivalent to `instantiateRev e (subst.extract beginIdx endIdx)`, but it does not allocate a new array.
|
||||
|
||||
This instantiates with the "forwards" indexing scheme (see the docstring for `Lean.Expr.instantiateRev` for an example).
|
||||
See also `Lean.Expr.instantiateRange`, which instantiates with the "backwards" indexing scheme.
|
||||
-/
|
||||
@[extern "lean_expr_instantiate_rev_range"]
|
||||
opaque instantiateRevRange (e : @& Expr) (beginIdx endIdx : @& Nat) (xs : @& Array Expr) : Expr
|
||||
opaque instantiateRevRange (e : @& Expr) (beginIdx endIdx : @& Nat) (subst : @& Array Expr) : Expr
|
||||
|
||||
/-- Replace free (or meta) variables `xs` with loose bound variables. -/
|
||||
@[extern "lean_expr_abstract"]
|
||||
|
||||
@@ -606,7 +606,7 @@ where
|
||||
let v' := v.getLevelOffset
|
||||
(u.getLevelOffset == v' || v'.isZero)
|
||||
&& u.getOffset ≥ v.getOffset
|
||||
termination_by _ u v => (u, v)
|
||||
termination_by (u, v)
|
||||
|
||||
end Level
|
||||
|
||||
|
||||
@@ -16,14 +16,15 @@ structure AbstractMVarsResult where
|
||||
namespace AbstractMVars
|
||||
|
||||
structure State where
|
||||
ngen : NameGenerator
|
||||
lctx : LocalContext
|
||||
mctx : MetavarContext
|
||||
nextParamIdx : Nat := 0
|
||||
paramNames : Array Name := #[]
|
||||
fvars : Array Expr := #[]
|
||||
lmap : HashMap LMVarId Level := {}
|
||||
emap : HashMap MVarId Expr := {}
|
||||
ngen : NameGenerator
|
||||
lctx : LocalContext
|
||||
mctx : MetavarContext
|
||||
nextParamIdx : Nat := 0
|
||||
paramNames : Array Name := #[]
|
||||
fvars : Array Expr := #[]
|
||||
lmap : HashMap LMVarId Level := {}
|
||||
emap : HashMap MVarId Expr := {}
|
||||
abstractLevels : Bool -- whether to abstract level mvars
|
||||
|
||||
abbrev M := StateM State
|
||||
|
||||
@@ -42,6 +43,8 @@ def mkFreshFVarId : M FVarId :=
|
||||
return { name := (← mkFreshId) }
|
||||
|
||||
private partial def abstractLevelMVars (u : Level) : M Level := do
|
||||
if !(← get).abstractLevels then
|
||||
return u
|
||||
if !u.hasMVar then
|
||||
return u
|
||||
else
|
||||
@@ -124,10 +127,13 @@ end AbstractMVars
|
||||
new fresh universe metavariables, and instantiate the `(m_i : A_i)` in the lambda-expression
|
||||
with new fresh metavariables.
|
||||
|
||||
If `levels := false`, then level metavariables are not abstracted.
|
||||
|
||||
Application: we use this method to cache the results of type class resolution. -/
|
||||
def abstractMVars (e : Expr) : MetaM AbstractMVarsResult := do
|
||||
def abstractMVars (e : Expr) (levels : Bool := true): MetaM AbstractMVarsResult := do
|
||||
let e ← instantiateMVars e
|
||||
let (e, s) := AbstractMVars.abstractExprMVars e { mctx := (← getMCtx), lctx := (← getLCtx), ngen := (← getNGen) }
|
||||
let (e, s) := AbstractMVars.abstractExprMVars e
|
||||
{ mctx := (← getMCtx), lctx := (← getLCtx), ngen := (← getNGen), abstractLevels := levels }
|
||||
setNGen s.ngen
|
||||
setMCtx s.mctx
|
||||
let e := s.lctx.mkLambda s.fvars e
|
||||
|
||||
@@ -164,10 +164,41 @@ def mkEqOfHEq (h : Expr) : MetaM Expr := do
|
||||
| _ =>
|
||||
throwAppBuilderException ``HEq.trans m!"heterogeneous equality proof expected{indentExpr h}"
|
||||
|
||||
/--
|
||||
If `e` is `@Eq.refl α a`, return `a`.
|
||||
-/
|
||||
def isRefl? (e : Expr) : Option Expr := do
|
||||
if e.isAppOfArity ``Eq.refl 2 then
|
||||
some e.appArg!
|
||||
else
|
||||
none
|
||||
|
||||
/--
|
||||
If `e` is `@congrArg α β a b f h`, return `α`, `f` and `h`.
|
||||
Also works if `e` can be turned into such an application (e.g. `congrFun`).
|
||||
-/
|
||||
def congrArg? (e : Expr) : MetaM (Option (Expr × Expr × Expr )) := do
|
||||
if e.isAppOfArity ``congrArg 6 then
|
||||
let #[α, _β, _a, _b, f, h] := e.getAppArgs | unreachable!
|
||||
return some (α, f, h)
|
||||
if e.isAppOfArity ``congrFun 6 then
|
||||
let #[α, β, _f, _g, h, a] := e.getAppArgs | unreachable!
|
||||
let α' ← withLocalDecl `x .default α fun x => do
|
||||
mkForallFVars #[x] (β.beta #[x])
|
||||
let f' ← withLocalDecl `x .default α' fun f => do
|
||||
mkLambdaFVars #[f] (f.app a)
|
||||
return some (α', f', h)
|
||||
return none
|
||||
|
||||
/-- Given `f : α → β` and `h : a = b`, returns a proof of `f a = f b`.-/
|
||||
def mkCongrArg (f h : Expr) : MetaM Expr := do
|
||||
if h.isAppOf ``Eq.refl then
|
||||
mkEqRefl (mkApp f h.appArg!)
|
||||
partial def mkCongrArg (f h : Expr) : MetaM Expr := do
|
||||
if let some a := isRefl? h then
|
||||
mkEqRefl (mkApp f a)
|
||||
else if let some (α, f₁, h₁) ← congrArg? h then
|
||||
-- Fuse nested `congrArg` for smaller proof terms, e.g. when using simp
|
||||
let f' ← withLocalDecl `x .default α fun x => do
|
||||
mkLambdaFVars #[x] (f.beta #[f₁.beta #[x]])
|
||||
mkCongrArg f' h₁
|
||||
else
|
||||
let hType ← infer h
|
||||
let fType ← infer f
|
||||
@@ -181,8 +212,13 @@ def mkCongrArg (f h : Expr) : MetaM Expr := do
|
||||
|
||||
/-- Given `h : f = g` and `a : α`, returns a proof of `f a = g a`.-/
|
||||
def mkCongrFun (h a : Expr) : MetaM Expr := do
|
||||
if h.isAppOf ``Eq.refl then
|
||||
mkEqRefl (mkApp h.appArg! a)
|
||||
if let some f := isRefl? h then
|
||||
mkEqRefl (mkApp f a)
|
||||
else if let some (α, f₁, h₁) ← congrArg? h then
|
||||
-- Fuse nested `congrArg` for smaller proof terms, e.g. when using simp
|
||||
let f' ← withLocalDecl `x .default α fun x => do
|
||||
mkLambdaFVars #[x] (f₁.beta #[x, a])
|
||||
mkCongrArg f' h₁
|
||||
else
|
||||
let hType ← infer h
|
||||
match hType.eq? with
|
||||
|
||||
@@ -1218,7 +1218,7 @@ def withLocalDeclD (name : Name) (type : Expr) (k : Expr → n α) : n α :=
|
||||
withLocalDecl name BinderInfo.default type k
|
||||
|
||||
/-- Append an array of free variables `xs` to the local context and execute `k xs`.
|
||||
declInfos takes the form of an array consisting of:
|
||||
`declInfos` takes the form of an array consisting of:
|
||||
- the name of the variable
|
||||
- the binder info of the variable
|
||||
- a type constructor for the variable, where the array consists of all of the free variables
|
||||
|
||||
@@ -419,7 +419,7 @@ where
|
||||
loop (i+1)
|
||||
else
|
||||
vs.push v
|
||||
termination_by loop i => vs.size - i
|
||||
termination_by vs.size - i
|
||||
|
||||
private partial def insertAux [BEq α] (keys : Array Key) (v : α) : Nat → Trie α → Trie α
|
||||
| i, .node vs cs =>
|
||||
|
||||
@@ -4,6 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
import Lean.Expr
|
||||
import Lean.ToExpr
|
||||
|
||||
namespace Lean.Meta
|
||||
|
||||
@@ -34,6 +35,17 @@ protected def Key.hash : Key → UInt64
|
||||
|
||||
instance : Hashable Key := ⟨Key.hash⟩
|
||||
|
||||
instance : ToExpr Key where
|
||||
toTypeExpr := mkConst ``Key
|
||||
toExpr k := match k with
|
||||
| .const n a => mkApp2 (mkConst ``Key.const) (toExpr n) (toExpr a)
|
||||
| .fvar id a => mkApp2 (mkConst ``Key.fvar) (toExpr id) (toExpr a)
|
||||
| .lit l => mkApp (mkConst ``Key.lit) (toExpr l)
|
||||
| .star => mkConst ``Key.star
|
||||
| .other => mkConst ``Key.other
|
||||
| .arrow => mkConst ``Key.arrow
|
||||
| .proj n i a => mkApp3 (mkConst ``Key.proj) (toExpr n) (toExpr i) (toExpr a)
|
||||
|
||||
/--
|
||||
Discrimination tree trie. See `DiscrTree`.
|
||||
-/
|
||||
|
||||
@@ -77,7 +77,7 @@ private def mkSimpleEqThm (declName : Name) : MetaM (Option Name) := do
|
||||
|
||||
/--
|
||||
Return equation theorems for the given declaration.
|
||||
By default, we not create equation theorems for nonrecursive definitions.
|
||||
By default, we do not create equation theorems for nonrecursive definitions.
|
||||
You can use `nonRec := true` to override this behavior, a dummy `rfl` proof is created on the fly.
|
||||
-/
|
||||
def getEqnsFor? (declName : Name) (nonRec := false) : MetaM (Option (Array Name)) := withLCtx {} {} do
|
||||
|
||||
@@ -11,6 +11,7 @@ import Lean.Elab.Tactic.Rewrite
|
||||
namespace Lean.Meta.AC
|
||||
open Lean.Data.AC
|
||||
open Lean.Elab.Tactic
|
||||
open Std
|
||||
|
||||
abbrev ACExpr := Lean.Data.AC.Expr
|
||||
|
||||
@@ -43,13 +44,13 @@ def getInstance (cls : Name) (exprs : Array Expr) : MetaM (Option Expr) := do
|
||||
| _ => return none
|
||||
|
||||
def preContext (expr : Expr) : MetaM (Option PreContext) := do
|
||||
if let some assoc := ←getInstance ``IsAssociative #[expr] then
|
||||
if let some assoc := ←getInstance ``Associative #[expr] then
|
||||
return some
|
||||
{ assoc,
|
||||
op := expr
|
||||
id := 0
|
||||
comm := ←getInstance ``IsCommutative #[expr]
|
||||
idem := ←getInstance ``IsIdempotent #[expr] }
|
||||
comm := ←getInstance ``Commutative #[expr]
|
||||
idem := ←getInstance ``IdempotentOp #[expr] }
|
||||
|
||||
return none
|
||||
|
||||
@@ -99,13 +100,14 @@ where
|
||||
mkContext (α : Expr) (u : Level) (vars : Array Expr) : MetaM (Array Bool × Expr) := do
|
||||
let arbitrary := vars[0]!
|
||||
let zero := mkLevelZeroEx ()
|
||||
let noneE := mkApp (mkConst ``Option.none [zero])
|
||||
let someE := mkApp2 (mkConst ``Option.some [zero])
|
||||
|
||||
let plift := mkApp (mkConst ``PLift [zero])
|
||||
let pliftUp := mkApp2 (mkConst ``PLift.up [zero])
|
||||
let noneE tp := mkApp (mkConst ``Option.none [zero]) (plift tp)
|
||||
let someE tp v := mkApp2 (mkConst ``Option.some [zero]) (plift tp) (pliftUp tp v)
|
||||
let vars ← vars.mapM fun x => do
|
||||
let isNeutral :=
|
||||
let isNeutralClass := mkApp3 (mkConst ``IsNeutral [u]) α preContext.op x
|
||||
match ←getInstance ``IsNeutral #[preContext.op, x] with
|
||||
let isNeutralClass := mkApp3 (mkConst ``LawfulIdentity [u]) α preContext.op x
|
||||
match ←getInstance ``LawfulIdentity #[preContext.op, x] with
|
||||
| none => (false, noneE isNeutralClass)
|
||||
| some isNeutral => (true, someE isNeutralClass isNeutral)
|
||||
|
||||
@@ -116,13 +118,13 @@ where
|
||||
let vars ← mkListLit (mkApp2 (mkConst ``Variable [u]) α preContext.op) vars
|
||||
|
||||
let comm :=
|
||||
let commClass := mkApp2 (mkConst ``IsCommutative [u]) α preContext.op
|
||||
let commClass := mkApp2 (mkConst ``Commutative [u]) α preContext.op
|
||||
match preContext.comm with
|
||||
| none => noneE commClass
|
||||
| some comm => someE commClass comm
|
||||
|
||||
let idem :=
|
||||
let idemClass := mkApp2 (mkConst ``IsIdempotent [u]) α preContext.op
|
||||
let idemClass := mkApp2 (mkConst ``IdempotentOp [u]) α preContext.op
|
||||
match preContext.idem with
|
||||
| none => noneE idemClass
|
||||
| some idem => someE idemClass idem
|
||||
@@ -130,12 +132,12 @@ where
|
||||
return (isNeutrals, mkApp7 (mkConst ``Lean.Data.AC.Context.mk [u]) α preContext.op preContext.assoc comm idem vars arbitrary)
|
||||
|
||||
convert : ACExpr → Expr
|
||||
| Data.AC.Expr.op l r => mkApp2 (mkConst ``Data.AC.Expr.op) (convert l) (convert r)
|
||||
| Data.AC.Expr.var x => mkApp (mkConst ``Data.AC.Expr.var) $ mkNatLit x
|
||||
| .op l r => mkApp2 (mkConst ``Data.AC.Expr.op) (convert l) (convert r)
|
||||
| .var x => mkApp (mkConst ``Data.AC.Expr.var) $ mkNatLit x
|
||||
|
||||
convertTarget (vars : Array Expr) : ACExpr → Expr
|
||||
| Data.AC.Expr.op l r => mkApp2 preContext.op (convertTarget vars l) (convertTarget vars r)
|
||||
| Data.AC.Expr.var x => vars[x]!
|
||||
| .op l r => mkApp2 preContext.op (convertTarget vars l) (convertTarget vars r)
|
||||
| .var x => vars[x]!
|
||||
|
||||
def rewriteUnnormalized (mvarId : MVarId) : MetaM Unit := do
|
||||
let simpCtx :=
|
||||
@@ -150,7 +152,7 @@ def rewriteUnnormalized (mvarId : MVarId) : MetaM Unit := do
|
||||
newGoal.refl
|
||||
where
|
||||
post (e : Expr) : SimpM Simp.Step := do
|
||||
let ctx ← read
|
||||
let ctx ← Simp.getContext
|
||||
match e, ctx.parent? with
|
||||
| bin op₁ l r, some (bin op₂ _ _) =>
|
||||
if ←isDefEq op₁ op₂ then
|
||||
|
||||
@@ -37,7 +37,7 @@ where
|
||||
let sizeOfEq ← mkLT sizeOf_lhs sizeOf_rhs
|
||||
let hlt ← mkFreshExprSyntheticOpaqueMVar sizeOfEq
|
||||
-- TODO: we only need the `sizeOf` simp theorems
|
||||
match (← simpTarget hlt.mvarId! { config.arith := true, simpTheorems := #[ (← getSimpTheorems) ] }).1 with
|
||||
match (← simpTarget hlt.mvarId! { config.arith := true, simpTheorems := #[ (← getSimpTheorems) ] } {}).1 with
|
||||
| some _ => return false
|
||||
| none =>
|
||||
let heq ← mkCongrArg sizeOf_lhs.appFn! (← mkEqSymm h)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user