mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-28 15:54:08 +00:00
Compare commits
92 Commits
sym_offset
...
paul/array
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42f7959726 | ||
|
|
30cc699f4f | ||
|
|
8405a20f41 | ||
|
|
e7f08fee8d | ||
|
|
0bf5875041 | ||
|
|
2b21653725 | ||
|
|
45755e629b | ||
|
|
cba10f46c2 | ||
|
|
6625c0770c | ||
|
|
af8f7b2f2c | ||
|
|
d4596af90f | ||
|
|
ffce1d6e3b | ||
|
|
42a0e92453 | ||
|
|
d4c74b3566 | ||
|
|
2e8afdf74d | ||
|
|
c7f941076e | ||
|
|
9185fd2a34 | ||
|
|
642863e8c5 | ||
|
|
62d2688579 | ||
|
|
e8870da205 | ||
|
|
a011c9c5dd | ||
|
|
a6a3df8af0 | ||
|
|
b44c7e161c | ||
|
|
0bcac0d46c | ||
|
|
1bf16f710e | ||
|
|
c3d753640a | ||
|
|
e94ed002b5 | ||
|
|
7564329f06 | ||
|
|
0336a8385b | ||
|
|
c6e530a4f1 | ||
|
|
97d427b32b | ||
|
|
bc1a22cc22 | ||
|
|
0e28043ec6 | ||
|
|
45862d5486 | ||
|
|
ba8c2ed4ee | ||
|
|
9e241a4087 | ||
|
|
e90f6f77db | ||
|
|
9deb9ab59d | ||
|
|
6de7100f69 | ||
|
|
6f409e0eea | ||
|
|
3de1cc54c5 | ||
|
|
a3755fe0a5 | ||
|
|
4c1e4a77b4 | ||
|
|
896da85304 | ||
|
|
11cd55b4f1 | ||
|
|
88823b27a6 | ||
|
|
c9facc8102 | ||
|
|
63d1b530ba | ||
|
|
3f09741fb9 | ||
|
|
9f9531fa13 | ||
|
|
dae0d6fa05 | ||
|
|
4a3401f69a | ||
|
|
4526cdda5f | ||
|
|
c4639150c1 | ||
|
|
37870c168b | ||
|
|
57003e5c79 | ||
|
|
b2f485e352 | ||
|
|
5e29d7660a | ||
|
|
567cf74f1b | ||
|
|
fa2ddf1c56 | ||
|
|
f9af240bc4 | ||
|
|
3bfeb0bc1f | ||
|
|
8447586fea | ||
|
|
470e3b7fd0 | ||
|
|
0a0323734b | ||
|
|
69b058dc82 | ||
|
|
2c48ae7dfb | ||
|
|
c81a8897a9 | ||
|
|
3bc63aefb7 | ||
|
|
fa40491c78 | ||
|
|
af438425d5 | ||
|
|
648e1b1877 | ||
|
|
f84aa23d6d | ||
|
|
6bec8adf16 | ||
|
|
16873fb123 | ||
|
|
34d8eeb3be | ||
|
|
f1cc85eb19 | ||
|
|
08e6f714ca | ||
|
|
b8f8dde0b3 | ||
|
|
b09e33f76b | ||
|
|
a95227c7d7 | ||
|
|
8258cfe2a1 | ||
|
|
94e8fd4845 | ||
|
|
9063adbd51 | ||
|
|
3e16f5332f | ||
|
|
974fdd85c4 | ||
|
|
e8a16dfcc8 | ||
|
|
ad43266357 | ||
|
|
9efb2bf35c | ||
|
|
9fbbe6554d | ||
|
|
db30cf3954 | ||
|
|
e9a1c9ef63 |
@@ -13,12 +13,54 @@ These comments explain the scripts' behavior, which repositories get special han
|
||||
## Arguments
|
||||
- `version`: The version to release (e.g., v4.24.0)
|
||||
|
||||
## Release Notes (Required for -rc1 releases)
|
||||
|
||||
For first release candidates (`-rc1`), you must create release notes BEFORE the reference-manual toolchain bump PR can be merged.
|
||||
|
||||
**Steps to create release notes:**
|
||||
|
||||
1. Generate the release notes:
|
||||
```bash
|
||||
cd /path/to/lean4
|
||||
python3 script/release_notes.py --since <previous_version> > /tmp/release-notes-<version>.md
|
||||
```
|
||||
Replace `<previous_version>` with the last stable release (e.g., `v4.27.0` when releasing `v4.28.0-rc1`).
|
||||
|
||||
2. Review `/tmp/release-notes-<version>.md` for common issues:
|
||||
- **Unterminated code blocks**: Look for code fences that aren't closed. Fetch original PR with `gh pr view <number>` to repair.
|
||||
- **Truncated descriptions**: Some may end mid-sentence. Complete them from the original PR.
|
||||
- **Markdown issues**: Other syntax problems that could cause parsing errors.
|
||||
|
||||
3. Create the release notes file in the reference-manual repository:
|
||||
- File path: `Manual/Releases/v<version>.lean` (e.g., `v4_28_0.lean`)
|
||||
- Use Verso format with proper imports and `#doc (Manual)` block
|
||||
- **Use `#` for headers, not `##`** (Verso uses level 1 for subsections)
|
||||
- **Use plain ` ``` ` not ` ```lean `** (the latter executes code)
|
||||
- **Wrap underscore identifiers in backticks**: `` `bv_decide` `` not `bv_decide`
|
||||
|
||||
4. Update `Manual/Releases.lean`:
|
||||
- Add import: `import Manual.Releases.«v4_28_0»`
|
||||
- Add include: `{include 0 Manual.Releases.«v4_28_0»}`
|
||||
|
||||
5. Build to verify: `lake build Manual.Releases.v4_28_0`
|
||||
|
||||
6. Create a **separate PR** for release notes (not bundled with toolchain bump):
|
||||
```bash
|
||||
git checkout -b v<version>-release-notes
|
||||
gh pr create --title "doc: add v<version> release notes"
|
||||
```
|
||||
|
||||
For subsequent RCs (`-rc2`, etc.) and stable releases, just update the version number in the existing release notes file title.
|
||||
|
||||
See `doc/dev/release_checklist.md` section "Writing the release notes" for full details.
|
||||
|
||||
## Process
|
||||
|
||||
1. Run `script/release_checklist.py {version}` to check the current status
|
||||
2. **CRITICAL: If preliminary lean4 checks fail, STOP immediately and alert the user**
|
||||
- Check for: release branch exists, CMake version correct, tag exists, release page exists, release notes exist
|
||||
- Check for: release branch exists, CMake version correct, tag exists, release page exists, release notes file exists
|
||||
- **IMPORTANT**: The release page is created AUTOMATICALLY by CI after pushing the tag - DO NOT create it manually
|
||||
- **IMPORTANT**: For -rc1 releases, release notes must be created before proceeding
|
||||
- Do NOT create any PRs or proceed with repository updates if these checks fail
|
||||
3. Create a todo list tracking all repositories that need updates
|
||||
4. **CRITICAL RULE: You can ONLY run `release_steps.py` for a repository if `release_checklist.py` explicitly says to do so**
|
||||
@@ -61,6 +103,15 @@ Every time you run `release_checklist.py`, you MUST:
|
||||
This summary should be provided EVERY time you run the checklist, not just after creating new PRs.
|
||||
The user needs to see the complete picture of what's waiting for review.
|
||||
|
||||
## Nightly Infrastructure
|
||||
|
||||
The nightly build system uses branches and tags across two repositories:
|
||||
|
||||
- `leanprover/lean4` has **branches** `nightly` and `nightly-with-mathlib` tracking the latest nightly builds
|
||||
- `leanprover/lean4-nightly` has **dated tags** like `nightly-2026-01-23`
|
||||
|
||||
When a nightly succeeds with mathlib, all three should point to the same commit. Don't confuse these: branches are in the main lean4 repo, dated tags are in lean4-nightly.
|
||||
|
||||
## Error Handling
|
||||
|
||||
**CRITICAL**: If something goes wrong or a command fails:
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -115,7 +115,7 @@ jobs:
|
||||
CMAKE_MAJOR=$(grep -E "^set\(LEAN_VERSION_MAJOR " src/CMakeLists.txt | grep -oE '[0-9]+')
|
||||
CMAKE_MINOR=$(grep -E "^set\(LEAN_VERSION_MINOR " src/CMakeLists.txt | grep -oE '[0-9]+')
|
||||
CMAKE_PATCH=$(grep -E "^set\(LEAN_VERSION_PATCH " src/CMakeLists.txt | grep -oE '[0-9]+')
|
||||
CMAKE_IS_RELEASE=$(grep -m 1 -E "^set\(LEAN_VERSION_IS_RELEASE " src/CMakeLists.txt | grep -oE '[0-9]+')
|
||||
CMAKE_IS_RELEASE=$(grep -m 1 -E "^set\(LEAN_VERSION_IS_RELEASE " src/CMakeLists.txt | sed -nE 's/^set\(LEAN_VERSION_IS_RELEASE ([0-9]+)\).*/\1/p')
|
||||
|
||||
# Expected values from tag parsing
|
||||
TAG_MAJOR="${{ steps.set-release.outputs.LEAN_VERSION_MAJOR }}"
|
||||
|
||||
62
.github/workflows/pr-release.yml
vendored
62
.github/workflows/pr-release.yml
vendored
@@ -62,42 +62,56 @@ jobs:
|
||||
git -C lean4.git remote add pr-releases https://foo:'${{ secrets.PR_RELEASES_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-pr-releases.git
|
||||
git -C lean4.git push -f pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
git -C lean4.git push -f pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-"${SHORT_SHA}"
|
||||
- name: Delete existing release if present
|
||||
- name: Delete existing releases if present
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
run: |
|
||||
# Try to delete any existing release for the current PR (just the version without the SHA suffix).
|
||||
# Delete any existing releases for this PR.
|
||||
# The short format release is always recreated with the latest commit.
|
||||
# The SHA-suffixed release should be unique per commit, but delete just in case.
|
||||
gh release delete --repo ${{ github.repository_owner }}/lean4-pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }} -y || true
|
||||
gh release delete --repo ${{ github.repository_owner }}/lean4-pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }} -y || true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
# Verify artifacts were downloaded (equivalent to fail_on_unmatched_files in the old action).
|
||||
- name: Verify release artifacts exist
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
files=(artifacts/*/*)
|
||||
if [ ${#files[@]} -eq 0 ]; then
|
||||
echo "::error::No artifacts found matching artifacts/*/*"
|
||||
exit 1
|
||||
fi
|
||||
echo "Found ${#files[@]} artifacts to upload:"
|
||||
printf '%s\n' "${files[@]}"
|
||||
# We use `gh release create` instead of `softprops/action-gh-release` because
|
||||
# the latter enumerates all releases to check for existing ones, which fails
|
||||
# when the repository has more than 10000 releases (GitHub API pagination limit).
|
||||
# Upstream fix: https://github.com/softprops/action-gh-release/pull/725
|
||||
- name: Release (short format)
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b
|
||||
with:
|
||||
name: Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# There are coredumps files here as well, but all in deeper subdirectories.
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
draft: false
|
||||
tag_name: pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
repository: ${{ github.repository_owner }}/lean4-pr-releases
|
||||
run: |
|
||||
# There are coredump files in deeper subdirectories; artifacts/*/* gets the release archives.
|
||||
gh release create \
|
||||
--repo ${{ github.repository_owner }}/lean4-pr-releases \
|
||||
--title "Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }}" \
|
||||
--notes "" \
|
||||
pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }} \
|
||||
artifacts/*/*
|
||||
env:
|
||||
# The token used here must have `workflow` privileges.
|
||||
GITHUB_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
|
||||
- name: Release (SHA-suffixed format)
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b
|
||||
with:
|
||||
name: Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }} (${{ steps.workflow-info.outputs.sourceHeadSha }})
|
||||
# There are coredumps files here as well, but all in deeper subdirectories.
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
draft: false
|
||||
tag_name: pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}
|
||||
repository: ${{ github.repository_owner }}/lean4-pr-releases
|
||||
run: |
|
||||
gh release create \
|
||||
--repo ${{ github.repository_owner }}/lean4-pr-releases \
|
||||
--title "Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }} (${{ steps.workflow-info.outputs.sourceHeadSha }})" \
|
||||
--notes "" \
|
||||
pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }} \
|
||||
artifacts/*/*
|
||||
env:
|
||||
# The token used here must have `workflow` privileges.
|
||||
GITHUB_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
GH_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
|
||||
- name: Report release status (short format)
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
|
||||
@@ -218,6 +218,11 @@ Please read https://leanprover-community.github.io/contribute/tags_and_branches.
|
||||
|
||||
# Writing the release notes
|
||||
|
||||
Release notes are only needed for the first release candidate (`-rc1`). For subsequent RCs and stable releases,
|
||||
just update the version number in the title of the existing release notes file.
|
||||
|
||||
## Generating the release notes
|
||||
|
||||
Release notes are automatically generated from the commit history, using `script/release_notes.py`.
|
||||
|
||||
Run this as `script/release_notes.py --since v4.6.0`, where `v4.6.0` is the *previous* release version.
|
||||
@@ -232,4 +237,113 @@ Some judgement is required here: ignore commits which look minor,
|
||||
but manually add items to the release notes for significant PRs that were rebase-merged.
|
||||
|
||||
There can also be pre-written entries in `./releases_drafts`, which should be all incorporated in the release notes and then deleted from the branch.
|
||||
|
||||
## Reviewing and fixing the generated markdown
|
||||
|
||||
Before adding the release notes to the reference manual, carefully review the generated markdown for these common issues:
|
||||
|
||||
1. **Unterminated code blocks**: PR descriptions sometimes have unclosed code fences. Look for code blocks
|
||||
that don't have a closing ` ``` `. If found, fetch the original PR description with `gh pr view <number>`
|
||||
and repair the code block with the complete content.
|
||||
|
||||
2. **Truncated descriptions**: Some PR descriptions may end abruptly mid-sentence. Review these and complete
|
||||
the descriptions based on the original PR.
|
||||
|
||||
3. **Markdown syntax issues**: Check for other markdown problems that could cause parsing errors.
|
||||
|
||||
## Creating the release notes file
|
||||
|
||||
The release notes go in `Manual/Releases/v4_7_0.lean` in the reference-manual repository.
|
||||
|
||||
The file structure must follow the Verso format:
|
||||
|
||||
```lean
|
||||
/-
|
||||
Copyright (c) 2025 Lean FRO LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Author: <Your Name>
|
||||
-/
|
||||
|
||||
import VersoManual
|
||||
import Manual.Meta
|
||||
import Manual.Meta.Markdown
|
||||
|
||||
open Manual
|
||||
open Verso.Genre
|
||||
open Verso.Genre.Manual
|
||||
open Verso.Genre.Manual.InlineLean
|
||||
|
||||
#doc (Manual) "Lean 4.7.0-rc1 (YYYY-MM-DD)" =>
|
||||
%%%
|
||||
tag := "release-v4.7.0"
|
||||
file := "v4.7.0"
|
||||
%%%
|
||||
|
||||
<release notes content here>
|
||||
```
|
||||
|
||||
**Important formatting rules for Verso:**
|
||||
- Use `#` for section headers inside the document, not `##` (Verso uses header level 1 for subsections)
|
||||
- Use plain ` ``` ` for code blocks, not ` ```lean ` (the latter will cause Lean to execute the code)
|
||||
- Identifiers with underscores like `bv_decide` should be wrapped in backticks: `` `bv_decide` ``
|
||||
(otherwise the underscore may be interpreted as markdown emphasis)
|
||||
|
||||
## Updating Manual/Releases.lean
|
||||
|
||||
After creating the release notes file, update `Manual/Releases.lean` to include it:
|
||||
|
||||
1. Add the import near the top with other version imports:
|
||||
```lean
|
||||
import Manual.Releases.«v4_7_0»
|
||||
```
|
||||
|
||||
2. Add the include statement after the other includes:
|
||||
```lean
|
||||
{include 0 Manual.Releases.«v4_7_0»}
|
||||
```
|
||||
|
||||
## Building and verifying
|
||||
|
||||
Build the release notes to check for errors:
|
||||
```bash
|
||||
lake build Manual.Releases.v4_7_0
|
||||
```
|
||||
|
||||
Common errors and fixes:
|
||||
- "Wrong header nesting - got ## but expected at most #": Change `##` to `#`
|
||||
- "Tactic 'X' failed" or similar: Code is being executed; change ` ```lean ` to ` ``` `
|
||||
- "'_'" errors: Underscore in identifier being parsed as emphasis; wrap in backticks
|
||||
|
||||
## Creating the PR
|
||||
|
||||
**Important: Timing with the reference-manual tag**
|
||||
|
||||
The reference-manual repository deploys documentation when a version tag is pushed. If you merge
|
||||
release notes AFTER the tag is created, the deployed documentation won't include them.
|
||||
|
||||
You have two options:
|
||||
|
||||
1. **Preferred**: Include the release notes in the same PR as the toolchain bump (or merge the
|
||||
release notes PR before creating the tag). This ensures the tag includes the release notes.
|
||||
|
||||
2. **If release notes are merged after the tag**: You must regenerate the tag to trigger a new deployment:
|
||||
```bash
|
||||
cd /path/to/reference-manual
|
||||
git fetch origin
|
||||
git tag -d v4.7.0-rc1 # Delete local tag
|
||||
git tag v4.7.0-rc1 origin/main # Create tag at current main (which has release notes)
|
||||
git push origin :refs/tags/v4.7.0-rc1 # Delete remote tag
|
||||
git push origin v4.7.0-rc1 # Push new tag (triggers Deploy workflow)
|
||||
```
|
||||
|
||||
If creating a separate PR for release notes:
|
||||
```bash
|
||||
git checkout -b v4.7.0-release-notes
|
||||
git add Manual/Releases/v4_7_0.lean Manual/Releases.lean
|
||||
git commit -m "doc: add v4.7.0 release notes"
|
||||
git push -u origin v4.7.0-release-notes
|
||||
gh pr create --title "doc: add v4.7.0 release notes" --body "This PR adds the release notes for Lean v4.7.0."
|
||||
```
|
||||
|
||||
See `./releases_drafts/README.md` for more information about pre-written release note entries.
|
||||
See `./releases_drafts/README.md` for more information.
|
||||
|
||||
@@ -29,7 +29,7 @@ def main (args : List String) : IO Unit := do
|
||||
if !msgs.toList.isEmpty then -- skip this file if there are parse errors
|
||||
msgs.forM fun msg => msg.toString >>= IO.println
|
||||
throw <| .userError "parse errors in file"
|
||||
let `(header| $[module%$moduleTk?]? $imps:import*) := header
|
||||
let `(header| $[module%$moduleTk?]? $[prelude%$preludeTk?]? $imps:import*) := header
|
||||
| throw <| .userError s!"unexpected header syntax of {path}"
|
||||
if moduleTk?.isSome then
|
||||
continue
|
||||
@@ -38,11 +38,11 @@ def main (args : List String) : IO Unit := do
|
||||
let startPos := header.raw.getPos? |>.getD parserState.pos
|
||||
|
||||
let dummyEnv ← mkEmptyEnvironment
|
||||
let (initCmd, parserState', _) :=
|
||||
let (initCmd, parserState', msgs') :=
|
||||
Parser.parseCommand inputCtx { env := dummyEnv, options := {} } parserState msgs
|
||||
|
||||
-- insert section if any trailing command
|
||||
if !initCmd.isOfKind ``Parser.Command.eoi then
|
||||
-- insert section if any trailing command (or error, which could be from an unknown command)
|
||||
if !initCmd.isOfKind ``Parser.Command.eoi || msgs'.hasErrors then
|
||||
let insertPos? :=
|
||||
-- put below initial module docstring if any
|
||||
guard (initCmd.isOfKind ``Parser.Command.moduleDoc) *> initCmd.getTailPos? <|>
|
||||
@@ -57,19 +57,21 @@ def main (args : List String) : IO Unit := do
|
||||
sec := "\n\n" ++ sec
|
||||
if insertPos?.isNone then
|
||||
sec := sec ++ "\n\n"
|
||||
text := text.extract 0 insertPos ++ sec ++ text.extract insertPos text.rawEndPos
|
||||
let insertPos := text.pos! insertPos
|
||||
text := text.extract text.startPos insertPos ++ sec ++ text.extract insertPos text.endPos
|
||||
|
||||
-- prepend each import with `public `
|
||||
for imp in imps.reverse do
|
||||
let insertPos := imp.raw.getPos?.get!
|
||||
let prfx := if doMeta then "public meta " else "public "
|
||||
text := text.extract 0 insertPos ++ prfx ++ text.extract insertPos text.rawEndPos
|
||||
let insertPos := text.pos! insertPos
|
||||
text := text.extract text.startPos insertPos ++ prfx ++ text.extract insertPos text.endPos
|
||||
|
||||
-- insert `module` header
|
||||
let mut initText := text.extract 0 startPos
|
||||
if !initText.trim.isEmpty then
|
||||
let mut initText := text.extract text.startPos (text.pos! startPos)
|
||||
if !initText.trimAscii.isEmpty then
|
||||
-- If there is a header comment, preserve it and put `module` in the line after
|
||||
initText := initText.trimRight ++ "\n"
|
||||
text := initText ++ "module\n\n" ++ text.extract startPos text.rawEndPos
|
||||
initText := initText.trimAsciiEnd.toString ++ "\n"
|
||||
text := initText ++ "module\n\n" ++ text.extract (text.pos! startPos) text.endPos
|
||||
|
||||
IO.FS.writeFile path text
|
||||
|
||||
@@ -185,6 +185,30 @@ def get_release_notes(tag_name):
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def check_release_notes_file_exists(toolchain, github_token):
|
||||
"""Check if the release notes file exists in the reference-manual repository.
|
||||
|
||||
For -rc1 releases, this checks that the release notes have been created.
|
||||
For subsequent RCs and stable releases, release notes should already exist.
|
||||
|
||||
Returns tuple (exists: bool, is_rc1: bool) where is_rc1 indicates if this is
|
||||
the first release candidate (when release notes need to be written).
|
||||
"""
|
||||
# Determine the release notes file path
|
||||
# e.g., v4.28.0-rc1 -> Manual/Releases/v4_28_0.lean
|
||||
base_version = strip_rc_suffix(toolchain.lstrip('v')) # "4.28.0"
|
||||
file_name = f"v{base_version.replace('.', '_')}.lean" # "v4_28_0.lean"
|
||||
file_path = f"Manual/Releases/{file_name}"
|
||||
|
||||
is_rc1 = toolchain.endswith("-rc1")
|
||||
|
||||
repo_url = "https://github.com/leanprover/reference-manual"
|
||||
|
||||
# Check if the file exists on main branch
|
||||
content = get_branch_content(repo_url, "main", file_path, github_token)
|
||||
|
||||
return (content is not None, is_rc1)
|
||||
|
||||
def get_branch_content(repo_url, branch, file_path, github_token):
|
||||
api_url = repo_url.replace("https://github.com/", "https://api.github.com/repos/") + f"/contents/{file_path}?ref={branch}"
|
||||
headers = {'Authorization': f'token {github_token}'} if github_token else {}
|
||||
@@ -501,6 +525,76 @@ def check_proofwidgets4_release(repo_url, target_toolchain, github_token):
|
||||
print(f" You will need to create and push a tag v0.0.{next_version}")
|
||||
return False
|
||||
|
||||
def check_reference_manual_release_title(repo_url, toolchain, pr_branch, github_token):
|
||||
"""Check if the reference-manual release notes title matches the release type.
|
||||
|
||||
For RC releases (e.g., v4.27.0-rc1), the title should contain the exact RC suffix.
|
||||
For final releases (e.g., v4.27.0), the title should NOT contain any "-rc".
|
||||
|
||||
Returns True if check passes or is not applicable, False if title needs updating.
|
||||
"""
|
||||
is_rc = is_release_candidate(toolchain)
|
||||
|
||||
# For RC releases, get the base version and RC suffix
|
||||
# e.g., "v4.27.0-rc1" -> version="4.27.0", rc_suffix="-rc1"
|
||||
if is_rc:
|
||||
parts = toolchain.lstrip('v').split('-', 1)
|
||||
version = parts[0]
|
||||
rc_suffix = '-' + parts[1] if len(parts) > 1 else ''
|
||||
else:
|
||||
version = toolchain.lstrip('v')
|
||||
rc_suffix = ''
|
||||
|
||||
# Construct the release notes file path (e.g., Manual/Releases/v4_27_0.lean for v4.27.0)
|
||||
file_name = f"v{version.replace('.', '_')}.lean" # "v4_27_0.lean"
|
||||
file_path = f"Manual/Releases/{file_name}"
|
||||
|
||||
# Try to get the file from the PR branch first, then fall back to main branch
|
||||
content = get_branch_content(repo_url, pr_branch, file_path, github_token)
|
||||
if content is None:
|
||||
# Try the default branch
|
||||
content = get_branch_content(repo_url, "main", file_path, github_token)
|
||||
|
||||
if content is None:
|
||||
print(f" ⚠️ Could not check release notes file: {file_path}")
|
||||
return True # Don't block on this
|
||||
|
||||
# Look for the #doc line with the title
|
||||
for line in content.splitlines():
|
||||
if line.strip().startswith('#doc') and 'Manual' in line:
|
||||
has_rc_in_title = '-rc' in line.lower()
|
||||
|
||||
if is_rc:
|
||||
# For RC releases, title should contain the exact RC suffix (e.g., "-rc1")
|
||||
# Use regex to match exact suffix followed by non-digit (to avoid -rc1 matching -rc10)
|
||||
# Pattern matches the RC suffix followed by a non-digit or end-of-string context
|
||||
# e.g., "-rc1" followed by space, quote, paren, or similar
|
||||
exact_match = re.search(rf'{re.escape(rc_suffix)}(?![0-9])', line, re.IGNORECASE)
|
||||
if exact_match:
|
||||
print(f" ✅ Release notes title correctly shows {rc_suffix}")
|
||||
return True
|
||||
elif has_rc_in_title:
|
||||
print(f" ❌ Release notes title shows wrong RC version (expected {rc_suffix})")
|
||||
print(f" Update {file_path} to use '{rc_suffix}' in the title")
|
||||
return False
|
||||
else:
|
||||
print(f" ❌ Release notes title missing RC suffix")
|
||||
print(f" Update {file_path} to include '{rc_suffix}' in the title")
|
||||
return False
|
||||
else:
|
||||
# For final releases, title should NOT contain -rc
|
||||
if has_rc_in_title:
|
||||
print(f" ❌ Release notes title still shows RC version")
|
||||
print(f" Update {file_path} to remove '-rcN' from the title")
|
||||
return False
|
||||
else:
|
||||
print(f" ✅ Release notes title is updated for final release")
|
||||
return True
|
||||
|
||||
# If we didn't find the #doc line, don't block
|
||||
print(f" ⚠️ Could not find release notes title in {file_path}")
|
||||
return True
|
||||
|
||||
def run_mathlib_verify_version_tags(toolchain, verbose=False):
|
||||
"""Run mathlib4's verify_version_tags.py script to validate the release tag.
|
||||
|
||||
@@ -644,6 +738,27 @@ def main():
|
||||
else:
|
||||
print(f" ✅ Release notes page title looks good ('{actual_title}').")
|
||||
|
||||
# Check if release notes file exists in reference-manual repository
|
||||
# For -rc1 releases, this is when release notes need to be written
|
||||
# For subsequent RCs and stable releases, they should already exist
|
||||
release_notes_exists, is_rc1 = check_release_notes_file_exists(toolchain, github_token)
|
||||
base_version = strip_rc_suffix(toolchain.lstrip('v'))
|
||||
release_notes_file = f"Manual/Releases/v{base_version.replace('.', '_')}.lean"
|
||||
|
||||
if not release_notes_exists:
|
||||
if is_rc1:
|
||||
print(f" ❌ Release notes file not found: {release_notes_file}")
|
||||
print(f" This is an -rc1 release, so release notes need to be written.")
|
||||
print(f" Run `script/release_notes.py --since <previous_version>` to generate them.")
|
||||
print(f" See doc/dev/release_checklist.md section 'Writing the release notes' for details.")
|
||||
lean4_success = False
|
||||
else:
|
||||
print(f" ❌ Release notes file not found: {release_notes_file}")
|
||||
print(f" Release notes should have been created for -rc1. Check the reference-manual repository.")
|
||||
lean4_success = False
|
||||
else:
|
||||
print(f" ✅ Release notes file exists: {release_notes_file}")
|
||||
|
||||
repo_status["lean4"] = lean4_success
|
||||
|
||||
# If the release page doesn't exist, skip repository checks and master branch checks
|
||||
@@ -709,6 +824,11 @@ def main():
|
||||
print(f" ⚠️ CI: {ci_message}")
|
||||
else:
|
||||
print(f" ❓ CI: {ci_message}")
|
||||
|
||||
# For reference-manual, check that the release notes title has been updated
|
||||
if name == "reference-manual":
|
||||
pr_branch = f"bump_to_{toolchain}"
|
||||
check_reference_manual_release_title(url, toolchain, pr_branch, github_token)
|
||||
else:
|
||||
print(f" ❌ PR with title '{pr_title}' does not exist")
|
||||
print(f" Run `script/release_steps.py {toolchain} {name}` to create it")
|
||||
|
||||
@@ -14,13 +14,6 @@ repositories:
|
||||
bump-branch: true
|
||||
dependencies: []
|
||||
|
||||
- name: verso
|
||||
url: https://github.com/leanprover/verso
|
||||
toolchain-tag: true
|
||||
stable-branch: false
|
||||
branch: main
|
||||
dependencies: []
|
||||
|
||||
- name: lean4checker
|
||||
url: https://github.com/leanprover/lean4checker
|
||||
toolchain-tag: true
|
||||
@@ -42,6 +35,14 @@ repositories:
|
||||
branch: main
|
||||
dependencies: []
|
||||
|
||||
- name: verso
|
||||
url: https://github.com/leanprover/verso
|
||||
toolchain-tag: true
|
||||
stable-branch: false
|
||||
branch: main
|
||||
dependencies:
|
||||
- plausible
|
||||
|
||||
- name: import-graph
|
||||
url: https://github.com/leanprover-community/import-graph
|
||||
toolchain-tag: true
|
||||
|
||||
@@ -10,7 +10,7 @@ endif()
|
||||
include(ExternalProject)
|
||||
project(LEAN CXX C)
|
||||
set(LEAN_VERSION_MAJOR 4)
|
||||
set(LEAN_VERSION_MINOR 28)
|
||||
set(LEAN_VERSION_MINOR 29)
|
||||
set(LEAN_VERSION_PATCH 0)
|
||||
set(LEAN_VERSION_IS_RELEASE 0) # This number is 1 in the release revision, and 0 otherwise.
|
||||
set(LEAN_SPECIAL_VERSION_DESC "" CACHE STRING "Additional version description like 'nightly-2018-03-11'")
|
||||
|
||||
@@ -30,3 +30,4 @@ public import Init.Data.Array.Erase
|
||||
public import Init.Data.Array.Zip
|
||||
public import Init.Data.Array.InsertIdx
|
||||
public import Init.Data.Array.Extract
|
||||
public import Init.Data.Array.MinMax
|
||||
|
||||
@@ -3065,6 +3065,18 @@ theorem foldl_eq_foldlM {f : β → α → β} {b} {xs : Array α} {start stop :
|
||||
theorem foldr_eq_foldrM {f : α → β → β} {b} {xs : Array α} {start stop : Nat} :
|
||||
xs.foldr f b start stop = (xs.foldrM (m := Id) (pure <| f · ·) b start stop).run := rfl
|
||||
|
||||
public theorem foldl_eq_foldl_extract {xs : Array α} {f : β → α → β} {init : β} :
|
||||
xs.foldl (init := init) (start := start) (stop := stop) f =
|
||||
(xs.extract start stop).foldl (init := init) f := by
|
||||
simp only [foldl_eq_foldlM]
|
||||
rw [foldlM_start_stop]
|
||||
|
||||
public theorem foldr_eq_foldr_extract {xs : Array α} {f : α → β → β} {init : β} :
|
||||
xs.foldr (init := init) (start := start) (stop := stop) f =
|
||||
(xs.extract stop start).foldr (init := init) f := by
|
||||
simp only [foldr_eq_foldrM]
|
||||
rw [foldrM_start_stop]
|
||||
|
||||
@[simp] theorem id_run_foldlM {f : β → α → Id β} {b} {xs : Array α} {start stop : Nat} :
|
||||
Id.run (xs.foldlM f b start stop) = xs.foldl (f · · |>.run) b start stop := rfl
|
||||
|
||||
|
||||
401
src/Init/Data/Array/MinMax.lean
Normal file
401
src/Init/Data/Array/MinMax.lean
Normal file
@@ -0,0 +1,401 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Paul Reichert
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Array.Bootstrap
|
||||
public import Init.Data.Array.Lemmas
|
||||
public import Init.Data.Array.DecidableEq
|
||||
import Init.Data.List.MinMax
|
||||
import Init.Data.List.ToArray
|
||||
|
||||
namespace Array
|
||||
|
||||
/-! ## Minima and maxima -/
|
||||
|
||||
/-! ### min -/
|
||||
|
||||
/--
|
||||
Returns the smallest element of a non-empty array.
|
||||
|
||||
Examples:
|
||||
* `#[4].min (by decide) = 4`
|
||||
* `#[1, 4, 2, 10, 6].min (by decide) = 1`
|
||||
-/
|
||||
public protected def min [Min α] (arr : Array α) (h : arr ≠ #[]) : α :=
|
||||
haveI : arr.size > 0 := by simp [Array.size_pos_iff, h]
|
||||
arr.foldl min arr[0] (start := 1)
|
||||
|
||||
/-! ### min? -/
|
||||
|
||||
/--
|
||||
Returns the smallest element of the array if it is not empty, or `none` if it is empty.
|
||||
|
||||
Examples:
|
||||
* `#[].min? = none`
|
||||
* `#[4].min? = some 4`
|
||||
* `#[1, 4, 2, 10, 6].min? = some 1`
|
||||
-/
|
||||
public protected def min? [Min α] (arr : Array α) : Option α :=
|
||||
if h : arr ≠ #[] then
|
||||
some (arr.min h)
|
||||
else
|
||||
none
|
||||
|
||||
/-! ### max -/
|
||||
|
||||
/--
|
||||
Returns the largest element of a non-empty array.
|
||||
|
||||
Examples:
|
||||
* `#[4].max (by decide) = 4`
|
||||
* `#[1, 4, 2, 10, 6].max (by decide) = 10`
|
||||
-/
|
||||
public protected def max [Max α] (arr : Array α) (h : arr ≠ #[]) : α :=
|
||||
haveI : arr.size > 0 := by simp [Array.size_pos_iff, h]
|
||||
arr.foldl max arr[0] (start := 1)
|
||||
|
||||
/-! ### max? -/
|
||||
|
||||
/--
|
||||
Returns the largest element of the array if it is not empty, or `none` if it is empty.
|
||||
|
||||
Examples:
|
||||
* `#[].max? = none`
|
||||
* `#[4].max? = some 4`
|
||||
* `#[1, 4, 2, 10, 6].max? = some 10`
|
||||
-/
|
||||
public protected def max? [Max α] (arr : Array α) : Option α :=
|
||||
if h : arr ≠ #[] then
|
||||
some (arr.max h)
|
||||
else
|
||||
none
|
||||
|
||||
/-! ### Compatibility with `List` -/
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem _root_.List.min_toArray [Min α] {l : List α} {h} :
|
||||
l.toArray.min h = l.min (by simpa [List.ne_nil_iff_length_pos] using h) := by
|
||||
let h' : l ≠ [] := by simpa [List.ne_nil_iff_length_pos] using h
|
||||
change l.toArray.min h = l.min h'
|
||||
rw [Array.min]
|
||||
· induction l
|
||||
· contradiction
|
||||
· rename_i x xs
|
||||
simp only [List.getElem_toArray, List.getElem_cons_zero, List.size_toArray, List.length_cons]
|
||||
rw [List.toArray_cons, foldl_eq_foldl_extract]
|
||||
rw [← Array.foldl_toList, Array.toList_extract, List.extract_eq_drop_take]
|
||||
simp [List.min]
|
||||
|
||||
public theorem _root_.List.min_eq_min_toArray [Min α] {l : List α} {h} :
|
||||
l.min h = l.toArray.min (by simpa [List.ne_nil_iff_length_pos] using h) := by
|
||||
simp
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem min_toList [Min α] {xs : Array α} {h} :
|
||||
xs.toList.min h = xs.min (by simpa [List.ne_nil_iff_length_pos] using h) := by
|
||||
cases xs; simp
|
||||
|
||||
public theorem min_eq_min_toList [Min α] {xs : Array α} {h} :
|
||||
xs.min h = xs.toList.min (by simpa [List.ne_nil_iff_length_pos] using h) := by
|
||||
simp
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem _root_.List.min?_toArray [Min α] {l : List α} :
|
||||
l.toArray.min? = l.min? := by
|
||||
rw [Array.min?]
|
||||
split
|
||||
· simp [List.min_toArray, List.min_eq_get_min?, - List.get_min?]
|
||||
· simp_all
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem min?_toList [Min α] {xs : Array α} :
|
||||
xs.toList.min? = xs.min? := by
|
||||
cases xs; simp
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem _root_.List.max_toArray [Max α] {l : List α} {h} :
|
||||
l.toArray.max h = l.max (by simpa [List.ne_nil_iff_length_pos] using h) := by
|
||||
let h' : l ≠ [] := by simpa [List.ne_nil_iff_length_pos] using h
|
||||
change l.toArray.max h = l.max h'
|
||||
rw [Array.max]
|
||||
· induction l
|
||||
· contradiction
|
||||
· rename_i x xs
|
||||
simp only [List.getElem_toArray, List.getElem_cons_zero, List.size_toArray, List.length_cons]
|
||||
rw [List.toArray_cons, foldl_eq_foldl_extract]
|
||||
rw [← Array.foldl_toList, Array.toList_extract, List.extract_eq_drop_take]
|
||||
simp [List.max]
|
||||
|
||||
public theorem _root_.List.max_eq_max_toArray [Max α] {l : List α} {h} :
|
||||
l.max h = l.toArray.max (by simpa [List.ne_nil_iff_length_pos] using h) := by
|
||||
simp
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem max_toList [Max α] {xs : Array α} {h} :
|
||||
xs.toList.max h = xs.max (by simpa [List.ne_nil_iff_length_pos] using h) := by
|
||||
cases xs; simp
|
||||
|
||||
public theorem max_eq_max_toList [Max α] {xs : Array α} {h} :
|
||||
xs.max h = xs.toList.max (by simpa [List.ne_nil_iff_length_pos] using h) := by
|
||||
simp
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem _root_.List.max?_toArray [Max α] {l : List α} :
|
||||
l.toArray.max? = l.max? := by
|
||||
rw [Array.max?]
|
||||
split
|
||||
· simp [List.max_toArray, List.max_eq_get_max?, - List.get_max?]
|
||||
· simp_all
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem max?_toList [Max α] {xs : Array α} :
|
||||
xs.toList.max? = xs.max? := by
|
||||
cases xs; simp
|
||||
|
||||
/-! ### Lemmas about `min?` -/
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem min?_empty [Min α] : (#[] : Array α).min? = none :=
|
||||
(rfl)
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem min?_singleton [Min α] {x : α} : #[x].min? = some x :=
|
||||
(rfl)
|
||||
|
||||
-- We don't put `@[simp]` on `min?_singleton_append'`,
|
||||
-- because the definition in terms of `foldl` is not useful for proofs.
|
||||
public theorem min?_singleton_append' [Min α] {xs : Array α} :
|
||||
(#[x] ++ xs).min? = some (xs.foldl min x) := by
|
||||
simp [← min?_toList, toList_append, List.min?]
|
||||
|
||||
@[simp]
|
||||
public theorem min?_singleton_append [Min α] [Std.Associative (min : α → α → α)] {xs : Array α} :
|
||||
(#[x] ++ xs).min? = some (xs.min?.elim x (min x)) := by
|
||||
simp [← min?_toList, toList_append, List.min?_cons]
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem min?_eq_none_iff {xs : Array α} [Min α] : xs.min? = none ↔ xs = #[] := by
|
||||
rcases xs with ⟨l⟩
|
||||
simp
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem isSome_min?_iff {xs : Array α} [Min α] : xs.min?.isSome ↔ xs ≠ #[] := by
|
||||
rcases xs with ⟨l⟩
|
||||
simp
|
||||
|
||||
@[grind .]
|
||||
public theorem isSome_min?_of_mem {xs : Array α} [Min α] {a : α} (h : a ∈ xs) :
|
||||
xs.min?.isSome := by
|
||||
rw [← min?_toList]
|
||||
apply List.isSome_min?_of_mem (a := a)
|
||||
simpa
|
||||
|
||||
public theorem isSome_min?_of_ne_empty [Min α] (xs : Array α) (h : xs ≠ #[]) : xs.min?.isSome := by
|
||||
rw [← min?_toList]
|
||||
apply List.isSome_min?_of_ne_nil
|
||||
simpa
|
||||
|
||||
public theorem min?_mem [Min α] [Std.MinEqOr α] (xs : Array α) (h : xs.min? = some a) : a ∈ xs := by
|
||||
rw [← min?_toList] at h
|
||||
simpa using List.min?_mem h
|
||||
|
||||
public theorem le_min?_iff [Min α] [LE α] [Std.LawfulOrderInf α] :
|
||||
{xs : Array α} → xs.min? = some a → ∀ {x}, x ≤ a ↔ ∀ b, b ∈ xs → x ≤ b := by
|
||||
intro xs h x
|
||||
simp only [← min?_toList] at h
|
||||
simpa using List.le_min?_iff h
|
||||
|
||||
public theorem min?_eq_some_iff [Min α] [LE α] {xs : Array α} [Std.IsLinearOrder α]
|
||||
[Std.LawfulOrderMin α] : xs.min? = some a ↔ a ∈ xs ∧ ∀ b, b ∈ xs → a ≤ b := by
|
||||
rcases xs with ⟨l⟩
|
||||
simpa using List.min?_eq_some_iff
|
||||
|
||||
public theorem min?_replicate [Min α] [Std.IdempotentOp (min : α → α → α)] {n : Nat} {a : α} :
|
||||
(replicate n a).min? = if n = 0 then none else some a := by
|
||||
rw [← List.toArray_replicate, List.min?_toArray, List.min?_replicate]
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem min?_replicate_of_pos [Min α] [Std.MinEqOr α] {n : Nat} {a : α} (h : 0 < n) :
|
||||
(replicate n a).min? = some a := by
|
||||
simp [min?_replicate, Nat.ne_of_gt h]
|
||||
|
||||
public theorem foldl_min [Min α] [Std.IdempotentOp (min : α → α → α)]
|
||||
[Std.Associative (min : α → α → α)] {xs : Array α} {a : α} :
|
||||
xs.foldl (init := a) min = min a (xs.min?.getD a) := by
|
||||
rcases xs with ⟨l⟩
|
||||
simp [List.foldl_min]
|
||||
|
||||
/-! ### Lemmas about `max?` -/
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem max?_empty [Max α] : (#[] : Array α).max? = none :=
|
||||
(rfl)
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem max?_singleton [Max α] {x : α} : #[x].max? = some x :=
|
||||
(rfl)
|
||||
|
||||
-- We don't put `@[simp]` on `max?_singleton_append'`,
|
||||
-- because the definition in terms of `foldl` is not useful for proofs.
|
||||
public theorem max?_singleton_append' [Max α] {xs : Array α} : (#[x] ++ xs).max? = some (xs.foldl max x) := by
|
||||
simp [← max?_toList, toList_append, List.max?]
|
||||
|
||||
@[simp]
|
||||
public theorem max?_singleton_append [Max α] [Std.Associative (max : α → α → α)] {xs : Array α} :
|
||||
(#[x] ++ xs).max? = some (xs.max?.elim x (max x)) := by
|
||||
simp [← max?_toList, toList_append, List.max?_cons]
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem max?_eq_none_iff {xs : Array α} [Max α] : xs.max? = none ↔ xs = #[] := by
|
||||
rcases xs with ⟨l⟩
|
||||
simp
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem isSome_max?_iff {xs : Array α} [Max α] : xs.max?.isSome ↔ xs ≠ #[] := by
|
||||
rcases xs with ⟨l⟩
|
||||
simp
|
||||
|
||||
@[grind .]
|
||||
public theorem isSome_max?_of_mem {xs : Array α} [Max α] {a : α} (h : a ∈ xs) :
|
||||
xs.max?.isSome := by
|
||||
rw [← max?_toList]
|
||||
apply List.isSome_max?_of_mem (a := a)
|
||||
simpa
|
||||
|
||||
public theorem isSome_max?_of_ne_empty [Max α] (xs : Array α) (h : xs ≠ #[]) : xs.max?.isSome := by
|
||||
rw [← max?_toList]
|
||||
apply List.isSome_max?_of_ne_nil
|
||||
simpa
|
||||
|
||||
public theorem max?_mem [Max α] [Std.MaxEqOr α] (xs : Array α) (h : xs.max? = some a) : a ∈ xs := by
|
||||
rw [← max?_toList] at h
|
||||
simpa using List.max?_mem h
|
||||
|
||||
public theorem max?_le_iff [Max α] [LE α] [Std.LawfulOrderSup α] :
|
||||
{xs : Array α} → xs.max? = some a → ∀ {x}, a ≤ x ↔ ∀ b, b ∈ xs → b ≤ x := by
|
||||
intro xs h x
|
||||
simp only [← max?_toList] at h
|
||||
simpa using List.max?_le_iff h
|
||||
|
||||
public theorem max?_eq_some_iff [Max α] [LE α] {xs : Array α} [Std.IsLinearOrder α]
|
||||
[Std.LawfulOrderMax α] : xs.max? = some a ↔ a ∈ xs ∧ ∀ b, b ∈ xs → b ≤ a := by
|
||||
rcases xs with ⟨l⟩
|
||||
simpa using List.max?_eq_some_iff
|
||||
|
||||
public theorem max?_replicate [Max α] [Std.IdempotentOp (max : α → α → α)] {n : Nat} {a : α} :
|
||||
(replicate n a).max? = if n = 0 then none else some a := by
|
||||
rw [← List.toArray_replicate, List.max?_toArray, List.max?_replicate]
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem max?_replicate_of_pos [Max α] [Std.MaxEqOr α] {n : Nat} {a : α} (h : 0 < n) :
|
||||
(replicate n a).max? = some a := by
|
||||
simp [max?_replicate, Nat.ne_of_gt h]
|
||||
|
||||
public theorem foldl_max [Max α] [Std.IdempotentOp (max : α → α → α)] [Std.Associative (max : α → α → α)]
|
||||
{xs : Array α} {a : α} : xs.foldl (init := a) max = max a (xs.max?.getD a) := by
|
||||
rcases xs with ⟨l⟩
|
||||
simp [List.foldl_max]
|
||||
|
||||
/-! ### Lemmas about `min` -/
|
||||
|
||||
@[simp, grind =]
|
||||
theorem min_singleton [Min α] {x : α} :
|
||||
#[x].min (ne_empty_of_size_eq_add_one rfl) = x := by
|
||||
(rfl)
|
||||
|
||||
public theorem min?_eq_some_min [Min α] : {xs : Array α} → (h : xs ≠ #[]) →
|
||||
xs.min? = some (xs.min h)
|
||||
| ⟨a::as⟩, _ => by simp [Array.min, Array.min?]
|
||||
|
||||
public theorem min_eq_get_min? [Min α] : (xs : Array α) → (h : xs ≠ #[]) →
|
||||
xs.min h = xs.min?.get (xs.isSome_min?_of_ne_empty h)
|
||||
| ⟨a::as⟩, _ => by simp [Array.min, Array.min?]
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem get_min? [Min α] {xs : Array α} {h : xs.min?.isSome} :
|
||||
xs.min?.get h = xs.min (isSome_min?_iff.mp h) := by
|
||||
simp [min?_eq_some_min (isSome_min?_iff.mp h)]
|
||||
|
||||
@[grind .]
|
||||
public theorem min_mem [Min α] [Std.MinEqOr α] {xs : Array α} (h : xs ≠ #[]) : xs.min h ∈ xs :=
|
||||
xs.min?_mem (min?_eq_some_min h)
|
||||
|
||||
@[grind .]
|
||||
public theorem min_le_of_mem [Min α] [LE α] [Std.IsLinearOrder α] [Std.LawfulOrderMin α]
|
||||
{xs : Array α} {a : α} (ha : a ∈ xs) :
|
||||
xs.min (ne_empty_of_mem ha) ≤ a :=
|
||||
(Array.min?_eq_some_iff.mp (min?_eq_some_min (ne_empty_of_mem ha))).right a ha
|
||||
|
||||
public protected theorem le_min_iff [Min α] [LE α] [Std.LawfulOrderInf α]
|
||||
{xs : Array α} (h : xs ≠ #[]) : ∀ {x}, x ≤ xs.min h ↔ ∀ b, b ∈ xs → x ≤ b :=
|
||||
le_min?_iff (min?_eq_some_min h)
|
||||
|
||||
public theorem min_eq_iff [Min α] [LE α] {xs : Array α} [Std.IsLinearOrder α] [Std.LawfulOrderMin α]
|
||||
(h : xs ≠ #[]) : xs.min h = a ↔ a ∈ xs ∧ ∀ b, b ∈ xs → a ≤ b := by
|
||||
simpa [min?_eq_some_min h] using (min?_eq_some_iff (xs := xs))
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem min_replicate [Min α] [Std.MinEqOr α] {n : Nat} {a : α} (h : (replicate n a) ≠ #[]) :
|
||||
(replicate n a).min h = a := by
|
||||
have n_pos : 0 < n := by simpa [Nat.ne_zero_iff_zero_lt] using h
|
||||
simpa [min?_eq_some_min h] using (min?_replicate_of_pos (a := a) n_pos)
|
||||
|
||||
public theorem foldl_min_eq_min [Min α] [Std.IdempotentOp (min : α → α → α)]
|
||||
[Std.Associative (min : α → α → α)] {xs : Array α} (h : xs ≠ #[]) {a : α} :
|
||||
xs.foldl min a = min a (xs.min h) := by
|
||||
simpa [min?_eq_some_min h] using foldl_min (xs := xs)
|
||||
|
||||
/-! ### Lemmas about `max` -/
|
||||
|
||||
@[simp, grind =]
|
||||
theorem max_singleton [Max α] {x : α} :
|
||||
#[x].max (ne_empty_of_size_eq_add_one rfl) = x := by
|
||||
(rfl)
|
||||
|
||||
public theorem max?_eq_some_max [Max α] : {xs : Array α} → (h : xs ≠ #[]) →
|
||||
xs.max? = some (xs.max h)
|
||||
| ⟨a::as⟩, _ => by simp [Array.max, Array.max?]
|
||||
|
||||
public theorem max_eq_get_max? [Max α] : (xs : Array α) → (h : xs ≠ #[]) →
|
||||
xs.max h = xs.max?.get (xs.isSome_max?_of_ne_empty h)
|
||||
| ⟨a::as⟩, _ => by simp [Array.max, Array.max?]
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem get_max? [Max α] {xs : Array α} {h : xs.max?.isSome} :
|
||||
xs.max?.get h = xs.max (isSome_max?_iff.mp h) := by
|
||||
simp [max?_eq_some_max (isSome_max?_iff.mp h)]
|
||||
|
||||
@[grind .]
|
||||
public theorem max_mem [Max α] [Std.MaxEqOr α] {xs : Array α} (h : xs ≠ #[]) : xs.max h ∈ xs :=
|
||||
xs.max?_mem (max?_eq_some_max h)
|
||||
|
||||
public protected theorem max_le_iff [Max α] [LE α] [Std.LawfulOrderSup α]
|
||||
{xs : Array α} (h : xs ≠ #[]) : ∀ {x}, xs.max h ≤ x ↔ ∀ b, b ∈ xs → b ≤ x :=
|
||||
max?_le_iff (max?_eq_some_max h)
|
||||
|
||||
public theorem max_eq_iff [Max α] [LE α] {xs : Array α} [Std.IsLinearOrder α] [Std.LawfulOrderMax α]
|
||||
(h : xs ≠ #[]) : xs.max h = a ↔ a ∈ xs ∧ ∀ b, b ∈ xs → b ≤ a := by
|
||||
simpa [max?_eq_some_max h] using (max?_eq_some_iff (xs := xs))
|
||||
|
||||
@[grind .]
|
||||
public theorem le_max_of_mem [Max α] [LE α] [Std.IsLinearOrder α] [Std.LawfulOrderMax α]
|
||||
{xs : Array α} {a : α} (ha : a ∈ xs) :
|
||||
a ≤ xs.max (ne_empty_of_mem ha) :=
|
||||
(Array.max?_eq_some_iff.mp (max?_eq_some_max (ne_empty_of_mem ha))).right a ha
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem max_replicate [Max α] [Std.MaxEqOr α] {n : Nat} {a : α} (h : (replicate n a) ≠ #[]) :
|
||||
(replicate n a).max h = a := by
|
||||
have n_pos : 0 < n := by simpa [Nat.ne_zero_iff_zero_lt] using h
|
||||
simpa [max?_eq_some_max h] using (max?_replicate_of_pos (a := a) n_pos)
|
||||
|
||||
public theorem foldl_max_eq_max [Max α] [Std.IdempotentOp (max : α → α → α)]
|
||||
[Std.Associative (max : α → α → α)] {xs : Array α} (h : xs ≠ #[]) {a : α} :
|
||||
xs.foldl max a = max a (xs.max h) := by
|
||||
simpa [max?_eq_some_max h] using foldl_max (xs := xs)
|
||||
|
||||
end Array
|
||||
@@ -9,3 +9,4 @@ prelude
|
||||
public import Init.Data.Char.Basic
|
||||
public import Init.Data.Char.Lemmas
|
||||
public import Init.Data.Char.Order
|
||||
public import Init.Data.Char.Ordinal
|
||||
|
||||
242
src/Init/Data/Char/Ordinal.lean
Normal file
242
src/Init/Data/Char/Ordinal.lean
Normal file
@@ -0,0 +1,242 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Author: Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Fin.OverflowAware
|
||||
public import Init.Data.UInt.Basic
|
||||
public import Init.Data.Function
|
||||
import Init.Data.Char.Lemmas
|
||||
import Init.Data.Char.Order
|
||||
import Init.Grind
|
||||
|
||||
/-!
|
||||
# Bijection between `Char` and `Fin Char.numCodePoints`
|
||||
|
||||
In this file, we construct a bijection between `Char` and `Fin Char.numCodePoints` and show that
|
||||
it is compatible with various operations. Since `Fin` is simpler than `Char` due to being based
|
||||
on natural numbers instead of `UInt32` and not having a hole in the middle (surrogate code points),
|
||||
this is sometimes useful to simplify reasoning about `Char`.
|
||||
|
||||
We use these declarations in the construction of `Char` ranges, see the module
|
||||
`Init.Data.Range.Polymorphic.Char`.
|
||||
-/
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
public section
|
||||
|
||||
namespace Char
|
||||
|
||||
/-- The number of surrogate code points. -/
|
||||
abbrev numSurrogates : Nat :=
|
||||
-- 0xe000 - 0xd800
|
||||
2048
|
||||
|
||||
/-- The size of the {name}`Char` type. -/
|
||||
abbrev numCodePoints : Nat :=
|
||||
-- 0x110000 - numSurrogates
|
||||
1112064
|
||||
|
||||
/--
|
||||
Packs {name}`Char` bijectively into {lean}`Fin Char.numCodePoints` by shifting code points which are
|
||||
greater than the surrogate code points by the number of surrogate code points.
|
||||
|
||||
The inverse of this function is called {name (scope := "Init.Data.Char.Ordinal")}`Char.ofOrdinal`.
|
||||
-/
|
||||
def ordinal (c : Char) : Fin Char.numCodePoints :=
|
||||
if h : c.val < 0xd800 then
|
||||
⟨c.val.toNat, by grind [UInt32.lt_iff_toNat_lt]⟩
|
||||
else
|
||||
⟨c.val.toNat - Char.numSurrogates, by grind [UInt32.lt_iff_toNat_lt]⟩
|
||||
|
||||
/--
|
||||
Unpacks {lean}`Fin Char.numCodePoints` bijectively to {name}`Char` by shifting code points which are
|
||||
greater than the surrogate code points by the number of surrogate code points.
|
||||
|
||||
The inverse of this function is called {name}`Char.ordinal`.
|
||||
-/
|
||||
def ofOrdinal (f : Fin Char.numCodePoints) : Char :=
|
||||
if h : (f : Nat) < 0xd800 then
|
||||
⟨UInt32.ofNatLT f (by grind), by grind [UInt32.toNat_ofNatLT]⟩
|
||||
else
|
||||
⟨UInt32.ofNatLT (f + Char.numSurrogates) (by grind), by grind [UInt32.toNat_ofNatLT]⟩
|
||||
|
||||
/--
|
||||
Computes the next {name}`Char`, skipping over surrogate code points (which are not valid
|
||||
{name}`Char`s) as necessary.
|
||||
|
||||
This function is specified by its interaction with {name}`Char.ordinal`, see
|
||||
{name (scope := "Init.Data.Char.Ordinal")}`Char.succ?_eq`.
|
||||
-/
|
||||
def succ? (c : Char) : Option Char :=
|
||||
if h₀ : c.val < 0xd7ff then
|
||||
some ⟨c.val + 1, by grind [UInt32.lt_iff_toNat_lt, UInt32.toNat_add]⟩
|
||||
else if h₁ : c.val = 0xd7ff then
|
||||
some ⟨0xe000, by decide⟩
|
||||
else if h₂ : c.val < 0x10ffff then
|
||||
some ⟨c.val + 1, by
|
||||
simp only [UInt32.lt_iff_toNat_lt, UInt32.reduceToNat, Nat.not_lt, ← UInt32.toNat_inj,
|
||||
UInt32.isValidChar, Nat.isValidChar, UInt32.toNat_add, Nat.reducePow] at *
|
||||
grind⟩
|
||||
else none
|
||||
|
||||
/--
|
||||
Computes the {name}`m`-th next {name}`Char`, skipping over surrogate code points (which are not
|
||||
valid {name}`Char`s) as necessary.
|
||||
|
||||
This function is specified by its interaction with {name}`Char.ordinal`, see
|
||||
{name (scope := "Init.Data.Char.Ordinal")}`Char.succMany?_eq`.
|
||||
-/
|
||||
def succMany? (m : Nat) (c : Char) : Option Char :=
|
||||
c.ordinal.addNat? m |>.map Char.ofOrdinal
|
||||
|
||||
@[grind =]
|
||||
theorem coe_ordinal {c : Char} :
|
||||
(c.ordinal : Nat) =
|
||||
if c.val < 0xd800 then
|
||||
c.val.toNat
|
||||
else
|
||||
c.val.toNat - Char.numSurrogates := by
|
||||
grind [Char.ordinal]
|
||||
|
||||
@[simp]
|
||||
theorem ordinal_zero : '\x00'.ordinal = 0 := by
|
||||
ext
|
||||
simp [coe_ordinal]
|
||||
|
||||
@[grind =]
|
||||
theorem val_ofOrdinal {f : Fin Char.numCodePoints} :
|
||||
(Char.ofOrdinal f).val =
|
||||
if h : (f : Nat) < 0xd800 then
|
||||
UInt32.ofNatLT f (by grind)
|
||||
else
|
||||
UInt32.ofNatLT (f + Char.numSurrogates) (by grind) := by
|
||||
grind [Char.ofOrdinal]
|
||||
|
||||
@[simp]
|
||||
theorem ofOrdinal_ordinal {c : Char} : Char.ofOrdinal c.ordinal = c := by
|
||||
ext
|
||||
simp only [val_ofOrdinal, coe_ordinal, UInt32.ofNatLT_add]
|
||||
split
|
||||
· grind [UInt32.lt_iff_toNat_lt, UInt32.ofNatLT_toNat]
|
||||
· rw [dif_neg]
|
||||
· simp only [← UInt32.toNat_inj, UInt32.toNat_add, UInt32.toNat_ofNatLT, Nat.reducePow]
|
||||
grind [UInt32.toNat_lt, UInt32.lt_iff_toNat_lt]
|
||||
· grind [UInt32.lt_iff_toNat_lt]
|
||||
|
||||
@[simp]
|
||||
theorem ordinal_ofOrdinal {f : Fin Char.numCodePoints} : (Char.ofOrdinal f).ordinal = f := by
|
||||
ext
|
||||
simp [coe_ordinal, val_ofOrdinal]
|
||||
split
|
||||
· rw [if_pos, UInt32.toNat_ofNatLT]
|
||||
simpa [UInt32.lt_iff_toNat_lt]
|
||||
· rw [if_neg, UInt32.toNat_add, UInt32.toNat_ofNatLT, UInt32.toNat_ofNatLT, Nat.mod_eq_of_lt,
|
||||
Nat.add_sub_cancel]
|
||||
· grind
|
||||
· simp only [UInt32.lt_iff_toNat_lt, UInt32.toNat_add, UInt32.toNat_ofNatLT, Nat.reducePow,
|
||||
UInt32.reduceToNat, Nat.not_lt]
|
||||
grind
|
||||
|
||||
@[simp]
|
||||
theorem ordinal_comp_ofOrdinal : Char.ordinal ∘ Char.ofOrdinal = id := by
|
||||
ext; simp
|
||||
|
||||
@[simp]
|
||||
theorem ofOrdinal_comp_ordinal : Char.ofOrdinal ∘ Char.ordinal = id := by
|
||||
ext; simp
|
||||
|
||||
@[simp]
|
||||
theorem ordinal_inj {c d : Char} : c.ordinal = d.ordinal ↔ c = d :=
|
||||
⟨fun h => by simpa using congrArg Char.ofOrdinal h, (· ▸ rfl)⟩
|
||||
|
||||
theorem ordinal_injective : Function.Injective Char.ordinal :=
|
||||
fun _ _ => ordinal_inj.1
|
||||
|
||||
@[simp]
|
||||
theorem ofOrdinal_inj {f g : Fin Char.numCodePoints} :
|
||||
Char.ofOrdinal f = Char.ofOrdinal g ↔ f = g :=
|
||||
⟨fun h => by simpa using congrArg Char.ordinal h, (· ▸ rfl)⟩
|
||||
|
||||
theorem ofOrdinal_injective : Function.Injective Char.ofOrdinal :=
|
||||
fun _ _ => ofOrdinal_inj.1
|
||||
|
||||
theorem ordinal_le_of_le {c d : Char} (h : c ≤ d) : c.ordinal ≤ d.ordinal := by
|
||||
simp only [le_def, UInt32.le_iff_toNat_le] at h
|
||||
simp only [Fin.le_def, coe_ordinal, UInt32.lt_iff_toNat_lt, UInt32.reduceToNat]
|
||||
grind
|
||||
|
||||
theorem ofOrdinal_le_of_le {f g : Fin Char.numCodePoints} (h : f ≤ g) :
|
||||
Char.ofOrdinal f ≤ Char.ofOrdinal g := by
|
||||
simp only [Fin.le_def] at h
|
||||
simp only [le_def, val_ofOrdinal, UInt32.ofNatLT_add, UInt32.le_iff_toNat_le]
|
||||
split
|
||||
· simp only [UInt32.toNat_ofNatLT]
|
||||
split
|
||||
· simpa
|
||||
· simp only [UInt32.toNat_add, UInt32.toNat_ofNatLT, Nat.reducePow]
|
||||
grind
|
||||
· simp only [UInt32.toNat_add, UInt32.toNat_ofNatLT, Nat.reducePow]
|
||||
rw [dif_neg (by grind)]
|
||||
simp only [UInt32.toNat_add, UInt32.toNat_ofNatLT, Nat.reducePow]
|
||||
grind
|
||||
|
||||
theorem le_iff_ordinal_le {c d : Char} : c ≤ d ↔ c.ordinal ≤ d.ordinal :=
|
||||
⟨ordinal_le_of_le, fun h => by simpa using ofOrdinal_le_of_le h⟩
|
||||
|
||||
theorem le_iff_ofOrdinal_le {f g : Fin Char.numCodePoints} :
|
||||
f ≤ g ↔ Char.ofOrdinal f ≤ Char.ofOrdinal g :=
|
||||
⟨ofOrdinal_le_of_le, fun h => by simpa using ordinal_le_of_le h⟩
|
||||
|
||||
theorem lt_iff_ordinal_lt {c d : Char} : c < d ↔ c.ordinal < d.ordinal := by
|
||||
simp only [Std.lt_iff_le_and_not_ge, le_iff_ordinal_le]
|
||||
|
||||
theorem lt_iff_ofOrdinal_lt {f g : Fin Char.numCodePoints} :
|
||||
f < g ↔ Char.ofOrdinal f < Char.ofOrdinal g := by
|
||||
simp only [Std.lt_iff_le_and_not_ge, le_iff_ofOrdinal_le]
|
||||
|
||||
theorem succ?_eq {c : Char} : c.succ? = (c.ordinal.addNat? 1).map Char.ofOrdinal := by
|
||||
fun_cases Char.succ? with
|
||||
| case1 h =>
|
||||
rw [Fin.addNat?_eq_some]
|
||||
· simp only [coe_ordinal, Option.map_some, Option.some.injEq, Char.ext_iff, val_ofOrdinal,
|
||||
UInt32.ofNatLT_add, UInt32.reduceOfNatLT]
|
||||
split
|
||||
· simp only [UInt32.ofNatLT_toNat, dite_eq_ite, left_eq_ite_iff, Nat.not_lt,
|
||||
Nat.reduceLeDiff, UInt32.left_eq_add]
|
||||
grind [UInt32.lt_iff_toNat_lt]
|
||||
· grind
|
||||
· simp [coe_ordinal]
|
||||
grind [UInt32.lt_iff_toNat_lt]
|
||||
| case2 =>
|
||||
rw [Fin.addNat?_eq_some]
|
||||
· simp [coe_ordinal, *, Char.ext_iff, val_ofOrdinal, numSurrogates]
|
||||
· simp [coe_ordinal, *, numCodePoints]
|
||||
| case3 =>
|
||||
rw [Fin.addNat?_eq_some]
|
||||
· simp only [coe_ordinal, Option.map_some, Option.some.injEq, Char.ext_iff, val_ofOrdinal,
|
||||
UInt32.ofNatLT_add, UInt32.reduceOfNatLT]
|
||||
split
|
||||
· grind
|
||||
· rw [dif_neg]
|
||||
· simp only [← UInt32.toNat_inj, UInt32.toNat_add, UInt32.reduceToNat, Nat.reducePow,
|
||||
UInt32.toNat_ofNatLT, Nat.mod_add_mod]
|
||||
grind [UInt32.lt_iff_toNat_lt, UInt32.toNat_inj]
|
||||
· grind [UInt32.lt_iff_toNat_lt, UInt32.toNat_inj]
|
||||
· grind [UInt32.lt_iff_toNat_lt]
|
||||
| case4 =>
|
||||
rw [eq_comm]
|
||||
grind [UInt32.lt_iff_toNat_lt]
|
||||
|
||||
theorem map_ordinal_succ? {c : Char} : c.succ?.map ordinal = c.ordinal.addNat? 1 := by
|
||||
simp [succ?_eq]
|
||||
|
||||
theorem succMany?_eq {m : Nat} {c : Char} :
|
||||
c.succMany? m = (c.ordinal.addNat? m).map Char.ofOrdinal := by
|
||||
rfl
|
||||
|
||||
end Char
|
||||
@@ -11,3 +11,4 @@ public import Init.Data.Fin.Log2
|
||||
public import Init.Data.Fin.Iterate
|
||||
public import Init.Data.Fin.Fold
|
||||
public import Init.Data.Fin.Lemmas
|
||||
public import Init.Data.Fin.OverflowAware
|
||||
|
||||
51
src/Init/Data/Fin/OverflowAware.lean
Normal file
51
src/Init/Data/Fin/OverflowAware.lean
Normal file
@@ -0,0 +1,51 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Author: Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Fin.Basic
|
||||
import Init.Data.Fin.Lemmas
|
||||
|
||||
set_option doc.verso true
|
||||
|
||||
public section
|
||||
|
||||
namespace Fin
|
||||
|
||||
/--
|
||||
Overflow-aware addition of a natural number to an element of {lean}`Fin n`.
|
||||
|
||||
Examples:
|
||||
* {lean}`(2 : Fin 3).addNat? 1 = (none : Option (Fin 3))`
|
||||
* {lean}`(2 : Fin 4).addNat? 1 = (some 3 : Option (Fin 4))`
|
||||
-/
|
||||
@[inline]
|
||||
protected def addNat? (i : Fin n) (m : Nat) : Option (Fin n) :=
|
||||
if h : i + m < n then some ⟨i + m, h⟩ else none
|
||||
|
||||
theorem addNat?_eq_some {i : Fin n} (h : i + m < n) : i.addNat? m = some ⟨i + m, h⟩ := by
|
||||
simp [Fin.addNat?, h]
|
||||
|
||||
theorem addNat?_eq_some_iff {i : Fin n} :
|
||||
i.addNat? m = some j ↔ i + m < n ∧ j = i + m := by
|
||||
simp only [Fin.addNat?]
|
||||
split <;> simp [Fin.ext_iff, eq_comm, *]
|
||||
|
||||
@[simp]
|
||||
theorem addNat?_eq_none_iff {i : Fin n} : i.addNat? m = none ↔ n ≤ i + m := by
|
||||
simp only [Fin.addNat?]
|
||||
split <;> simp_all [Nat.not_lt]
|
||||
|
||||
@[simp]
|
||||
theorem addNat?_zero {i : Fin n} : i.addNat? 0 = some i := by
|
||||
simp [addNat?_eq_some_iff]
|
||||
|
||||
@[grind =]
|
||||
theorem addNat?_eq_dif {i : Fin n} :
|
||||
i.addNat? m = if h : i + m < n then some ⟨i + m, h⟩ else none := by
|
||||
rfl
|
||||
|
||||
end Fin
|
||||
@@ -1447,4 +1447,12 @@ instance : LawfulOrderLT Int where
|
||||
lt_iff := by
|
||||
simp [← Int.not_le, Decidable.imp_iff_not_or, Std.Total.total]
|
||||
|
||||
instance : LawfulOrderLeftLeaningMin Int where
|
||||
min_eq_left _ _ := Int.min_eq_left
|
||||
min_eq_right _ _ h := Int.min_eq_right (le_of_lt (not_le.1 h))
|
||||
|
||||
instance : LawfulOrderLeftLeaningMax Int where
|
||||
max_eq_left _ _ := Int.max_eq_left
|
||||
max_eq_right _ _ h := Int.max_eq_right (le_of_lt (not_le.1 h))
|
||||
|
||||
end Int
|
||||
|
||||
@@ -29,7 +29,11 @@ open Nat
|
||||
|
||||
/-! ### min? -/
|
||||
|
||||
@[simp] theorem min?_nil [Min α] : ([] : List α).min? = none := rfl
|
||||
@[simp, grind =] theorem min?_nil [Min α] : ([] : List α).min? = none := rfl
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem min?_singleton [Min α] {x : α} : [x].min? = some x :=
|
||||
(rfl)
|
||||
|
||||
-- We don't put `@[simp]` on `min?_cons'`,
|
||||
-- because the definition in terms of `foldl` is not useful for proofs.
|
||||
@@ -39,9 +43,14 @@ theorem min?_cons' [Min α] {xs : List α} : (x :: xs).min? = some (foldl min x
|
||||
(x :: xs).min? = some (xs.min?.elim x (min x)) := by
|
||||
cases xs <;> simp [min?_cons', foldl_assoc]
|
||||
|
||||
@[simp] theorem min?_eq_none_iff {xs : List α} [Min α] : xs.min? = none ↔ xs = [] := by
|
||||
@[simp, grind =] theorem min?_eq_none_iff {xs : List α} [Min α] : xs.min? = none ↔ xs = [] := by
|
||||
cases xs <;> simp [min?]
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem isSome_min?_iff {xs : List α} [Min α] : xs.min?.isSome ↔ xs ≠ [] := by
|
||||
cases xs <;> simp [min?]
|
||||
|
||||
@[grind .]
|
||||
theorem isSome_min?_of_mem {l : List α} [Min α] {a : α} (h : a ∈ l) :
|
||||
l.min?.isSome := by
|
||||
cases l <;> simp_all [min?_cons']
|
||||
@@ -143,7 +152,8 @@ theorem min?_replicate [Min α] [Std.IdempotentOp (min : α → α → α)] {n :
|
||||
| zero => rfl
|
||||
| succ n ih => cases n <;> simp_all [replicate_succ, min?_cons', Std.IdempotentOp.idempotent]
|
||||
|
||||
@[simp] theorem min?_replicate_of_pos [Min α] [MinEqOr α] {n : Nat} {a : α} (h : 0 < n) :
|
||||
@[simp, grind =]
|
||||
theorem min?_replicate_of_pos [Min α] [MinEqOr α] {n : Nat} {a : α} (h : 0 < n) :
|
||||
(replicate n a).min? = some a := by
|
||||
simp [min?_replicate, Nat.ne_of_gt h]
|
||||
|
||||
@@ -160,6 +170,11 @@ theorem foldl_min [Min α] [Std.IdempotentOp (min : α → α → α)] [Std.Asso
|
||||
|
||||
/-! ### min -/
|
||||
|
||||
@[simp, grind =]
|
||||
theorem min_singleton [Min α] {x : α} :
|
||||
[x].min (cons_ne_nil _ _) = x := by
|
||||
(rfl)
|
||||
|
||||
theorem min?_eq_some_min [Min α] : {l : List α} → (hl : l ≠ []) →
|
||||
l.min? = some (l.min hl)
|
||||
| a::as, _ => by simp [List.min, List.min?_cons']
|
||||
@@ -168,15 +183,22 @@ theorem min_eq_get_min? [Min α] : (l : List α) → (hl : l ≠ []) →
|
||||
l.min hl = l.min?.get (isSome_min?_of_ne_nil hl)
|
||||
| a::as, _ => by simp [List.min, List.min?_cons']
|
||||
|
||||
@[simp, grind =]
|
||||
theorem get_min? [Min α] {l : List α} {h : l.min?.isSome} :
|
||||
l.min?.get h = l.min (isSome_min?_iff.mp h) := by
|
||||
simp [min?_eq_some_min (isSome_min?_iff.mp h)]
|
||||
|
||||
theorem min_eq_head {α : Type u} [Min α] {l : List α} (hl : l ≠ [])
|
||||
(h : l.Pairwise (fun a b => min a b = a)) : l.min hl = l.head hl := by
|
||||
apply Option.some.inj
|
||||
rw [← min?_eq_some_min, ← head?_eq_some_head]
|
||||
exact min?_eq_head? h
|
||||
|
||||
@[grind .]
|
||||
theorem min_mem [Min α] [MinEqOr α] {l : List α} (hl : l ≠ []) : l.min hl ∈ l :=
|
||||
min?_mem (min?_eq_some_min hl)
|
||||
|
||||
@[grind .]
|
||||
theorem min_le_of_mem [Min α] [LE α] [Std.IsLinearOrder α] [Std.LawfulOrderMin α]
|
||||
{l : List α} {a : α} (ha : a ∈ l) :
|
||||
l.min (ne_nil_of_mem ha) ≤ a :=
|
||||
@@ -190,7 +212,7 @@ theorem min_eq_iff [Min α] [LE α] {l : List α} [IsLinearOrder α] [LawfulOrde
|
||||
l.min hl = a ↔ a ∈ l ∧ ∀ b, b ∈ l → a ≤ b := by
|
||||
simpa [min?_eq_some_min hl] using (min?_eq_some_iff (xs := l))
|
||||
|
||||
@[simp] theorem min_replicate [Min α] [MinEqOr α] {n : Nat} {a : α} (h : replicate n a ≠ []) :
|
||||
@[simp, grind =] theorem min_replicate [Min α] [MinEqOr α] {n : Nat} {a : α} (h : replicate n a ≠ []) :
|
||||
(replicate n a).min h = a := by
|
||||
have n_pos : 0 < n := Nat.pos_of_ne_zero (fun hn => by simp [hn] at h)
|
||||
simpa [min?_eq_some_min h] using (min?_replicate_of_pos (a := a) n_pos)
|
||||
@@ -202,7 +224,11 @@ theorem foldl_min_eq_min [Min α] [Std.IdempotentOp (min : α → α → α)] [S
|
||||
|
||||
/-! ### max? -/
|
||||
|
||||
@[simp] theorem max?_nil [Max α] : ([] : List α).max? = none := rfl
|
||||
@[simp, grind =] theorem max?_nil [Max α] : ([] : List α).max? = none := rfl
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem max?_singleton [Max α] {x : α} : [x].max? = some x :=
|
||||
(rfl)
|
||||
|
||||
-- We don't put `@[simp]` on `max?_cons'`,
|
||||
-- because the definition in terms of `foldl` is not useful for proofs.
|
||||
@@ -212,9 +238,14 @@ theorem max?_cons' [Max α] {xs : List α} : (x :: xs).max? = some (foldl max x
|
||||
(x :: xs).max? = some (xs.max?.elim x (max x)) := by
|
||||
cases xs <;> simp [max?_cons', foldl_assoc]
|
||||
|
||||
@[simp] theorem max?_eq_none_iff {xs : List α} [Max α] : xs.max? = none ↔ xs = [] := by
|
||||
@[simp, grind =] theorem max?_eq_none_iff {xs : List α} [Max α] : xs.max? = none ↔ xs = [] := by
|
||||
cases xs <;> simp [max?]
|
||||
|
||||
@[simp, grind =]
|
||||
public theorem isSome_max?_iff {xs : List α} [Max α] : xs.max?.isSome ↔ xs ≠ [] := by
|
||||
cases xs <;> simp [max?]
|
||||
|
||||
@[grind .]
|
||||
theorem isSome_max?_of_mem {l : List α} [Max α] {a : α} (h : a ∈ l) :
|
||||
l.max?.isSome := by
|
||||
cases l <;> simp_all [max?_cons']
|
||||
@@ -329,7 +360,8 @@ theorem max?_replicate [Max α] [Std.IdempotentOp (max : α → α → α)] {n :
|
||||
| zero => rfl
|
||||
| succ n ih => cases n <;> simp_all [replicate_succ, max?_cons', Std.IdempotentOp.idempotent]
|
||||
|
||||
@[simp] theorem max?_replicate_of_pos [Max α] [MaxEqOr α] {n : Nat} {a : α} (h : 0 < n) :
|
||||
@[simp, grind =]
|
||||
theorem max?_replicate_of_pos [Max α] [MaxEqOr α] {n : Nat} {a : α} (h : 0 < n) :
|
||||
(replicate n a).max? = some a := by
|
||||
simp [max?_replicate, Nat.ne_of_gt h]
|
||||
|
||||
@@ -346,6 +378,11 @@ theorem foldl_max [Max α] [Std.IdempotentOp (max : α → α → α)] [Std.Asso
|
||||
|
||||
/-! ### max -/
|
||||
|
||||
@[simp, grind =]
|
||||
theorem max_singleton [Max α] {x : α} :
|
||||
[x].max (cons_ne_nil _ _) = x := by
|
||||
(rfl)
|
||||
|
||||
theorem max?_eq_some_max [Max α] : {l : List α} → (hl : l ≠ []) →
|
||||
l.max? = some (l.max hl)
|
||||
| a::as, _ => by simp [List.max, List.max?_cons']
|
||||
@@ -354,12 +391,18 @@ theorem max_eq_get_max? [Max α] : (l : List α) → (hl : l ≠ []) →
|
||||
l.max hl = l.max?.get (isSome_max?_of_ne_nil hl)
|
||||
| a::as, _ => by simp [List.max, List.max?_cons']
|
||||
|
||||
@[simp, grind =]
|
||||
theorem get_max? [Max α] {l : List α} {h : l.max?.isSome} :
|
||||
l.max?.get h = l.max (isSome_max?_iff.mp h) := by
|
||||
simp [max?_eq_some_max (isSome_max?_iff.mp h)]
|
||||
|
||||
theorem max_eq_head {α : Type u} [Max α] {l : List α} (hl : l ≠ [])
|
||||
(h : l.Pairwise (fun a b => max a b = a)) : l.max hl = l.head hl := by
|
||||
apply Option.some.inj
|
||||
rw [← max?_eq_some_max, ← head?_eq_some_head]
|
||||
exact max?_eq_head? h
|
||||
|
||||
@[grind .]
|
||||
theorem max_mem [Max α] [MaxEqOr α] {l : List α} (hl : l ≠ []) : l.max hl ∈ l :=
|
||||
max?_mem (max?_eq_some_max hl)
|
||||
|
||||
@@ -371,12 +414,13 @@ theorem max_eq_iff [Max α] [LE α] {l : List α} [IsLinearOrder α] [LawfulOrde
|
||||
l.max hl = a ↔ a ∈ l ∧ ∀ b, b ∈ l → b ≤ a := by
|
||||
simpa [max?_eq_some_max hl] using (max?_eq_some_iff (xs := l))
|
||||
|
||||
@[grind .]
|
||||
theorem le_max_of_mem [Max α] [LE α] [Std.IsLinearOrder α] [Std.LawfulOrderMax α]
|
||||
{l : List α} {a : α} (ha : a ∈ l) :
|
||||
a ≤ l.max (List.ne_nil_of_mem ha) :=
|
||||
(max?_eq_some_iff.mp (max?_eq_some_max (List.ne_nil_of_mem ha))).right a ha
|
||||
|
||||
@[simp] theorem max_replicate [Max α] [MaxEqOr α] {n : Nat} {a : α} (h : replicate n a ≠ []) :
|
||||
@[simp, grind =] theorem max_replicate [Max α] [MaxEqOr α] {n : Nat} {a : α} (h : replicate n a ≠ []) :
|
||||
(replicate n a).max h = a := by
|
||||
have n_pos : 0 < n := Nat.pos_of_ne_zero (fun hn => by simp [hn] at h)
|
||||
simpa [max?_eq_some_max h] using (max?_replicate_of_pos (a := a) n_pos)
|
||||
|
||||
@@ -15,3 +15,4 @@ public import Init.Data.Option.Attach
|
||||
public import Init.Data.Option.List
|
||||
public import Init.Data.Option.Monadic
|
||||
public import Init.Data.Option.Array
|
||||
public import Init.Data.Option.Function
|
||||
|
||||
26
src/Init/Data/Option/Function.lean
Normal file
26
src/Init/Data/Option/Function.lean
Normal file
@@ -0,0 +1,26 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Function
|
||||
import Init.Data.Option.Lemmas
|
||||
|
||||
public section
|
||||
|
||||
namespace Option
|
||||
|
||||
theorem map_injective {f : α → β} (hf : Function.Injective f) :
|
||||
Function.Injective (Option.map f) := by
|
||||
intros a b hab
|
||||
cases a <;> cases b
|
||||
· simp
|
||||
· simp at hab
|
||||
· simp at hab
|
||||
· simp only [map_some, some.injEq] at hab
|
||||
simpa using hf hab
|
||||
|
||||
end Option
|
||||
@@ -307,12 +307,20 @@ theorem map_id' {x : Option α} : (x.map fun a => a) = x := congrFun map_id x
|
||||
|
||||
theorem map_id_apply' {α : Type u} {x : Option α} : Option.map (fun (a : α) => a) x = x := by simp
|
||||
|
||||
/-- See `Option.apply_get` for a version that can be rewritten in the reverse direction. -/
|
||||
@[simp, grind =] theorem get_map {f : α → β} {o : Option α} {h : (o.map f).isSome} :
|
||||
(o.map f).get h = f (o.get (by simpa using h)) := by
|
||||
cases o with
|
||||
| none => simp at h
|
||||
| some a => simp
|
||||
|
||||
/-- See `Option.get_map` for a version that can be rewritten in the reverse direction. -/
|
||||
theorem apply_get {f : α → β} {o : Option α} {h} :
|
||||
f (o.get h) = (o.map f).get (by simp [h]) := by
|
||||
cases o
|
||||
· simp at h
|
||||
· simp
|
||||
|
||||
@[simp] theorem map_map (h : β → γ) (g : α → β) (x : Option α) :
|
||||
(x.map g).map h = x.map (h ∘ g) := by
|
||||
cases x <;> simp only [map_none, map_some, ·∘·]
|
||||
@@ -732,6 +740,11 @@ theorem get_merge {o o' : Option α} {f : α → α → α} {i : α} [Std.Lawful
|
||||
theorem elim_guard : (guard p a).elim b f = if p a then f a else b := by
|
||||
cases h : p a <;> simp [*, guard]
|
||||
|
||||
@[simp]
|
||||
theorem Option.elim_map {f : α → β} {g' : γ} {g : β → γ} (o : Option α) :
|
||||
(o.map f).elim g' g = o.elim g' (g ∘ f) := by
|
||||
cases o <;> simp
|
||||
|
||||
-- I don't see how to construct a good grind pattern to instantiate this.
|
||||
@[simp] theorem getD_map (f : α → β) (x : α) (o : Option α) :
|
||||
(o.map f).getD (f x) = f (getD o x) := by cases o <;> rfl
|
||||
|
||||
@@ -10,7 +10,10 @@ public import Init.Data.Range.Polymorphic.Basic
|
||||
public import Init.Data.Range.Polymorphic.Iterators
|
||||
public import Init.Data.Range.Polymorphic.Stream
|
||||
public import Init.Data.Range.Polymorphic.Lemmas
|
||||
public import Init.Data.Range.Polymorphic.Map
|
||||
|
||||
public import Init.Data.Range.Polymorphic.Fin
|
||||
public import Init.Data.Range.Polymorphic.Char
|
||||
public import Init.Data.Range.Polymorphic.Nat
|
||||
public import Init.Data.Range.Polymorphic.Int
|
||||
public import Init.Data.Range.Polymorphic.BitVec
|
||||
|
||||
79
src/Init/Data/Range/Polymorphic/Char.lean
Normal file
79
src/Init/Data/Range/Polymorphic/Char.lean
Normal file
@@ -0,0 +1,79 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Author: Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Char.Ordinal
|
||||
public import Init.Data.Range.Polymorphic.Fin
|
||||
import Init.Data.Range.Polymorphic.Lemmas
|
||||
import Init.Data.Range.Polymorphic.Map
|
||||
import Init.Data.Char.Order
|
||||
|
||||
open Std Std.PRange Std.PRange.UpwardEnumerable
|
||||
|
||||
namespace Char
|
||||
|
||||
public instance : UpwardEnumerable Char where
|
||||
succ?
|
||||
succMany?
|
||||
|
||||
@[simp]
|
||||
public theorem pRangeSucc?_eq : PRange.succ? (α := Char) = Char.succ? := rfl
|
||||
|
||||
@[simp]
|
||||
public theorem pRangeSuccMany?_eq : PRange.succMany? (α := Char) = Char.succMany? := rfl
|
||||
|
||||
public instance : Rxc.HasSize Char where
|
||||
size lo hi := Rxc.HasSize.size lo.ordinal hi.ordinal
|
||||
|
||||
public instance : Rxo.HasSize Char where
|
||||
size lo hi := Rxo.HasSize.size lo.ordinal hi.ordinal
|
||||
|
||||
public instance : Rxi.HasSize Char where
|
||||
size hi := Rxi.HasSize.size hi.ordinal
|
||||
|
||||
public instance : Least? Char where
|
||||
least? := some '\x00'
|
||||
|
||||
@[simp]
|
||||
public theorem least?_eq : Least?.least? (α := Char) = some '\x00' := rfl
|
||||
|
||||
def map : Map Char (Fin Char.numCodePoints) where
|
||||
toFun := Char.ordinal
|
||||
injective := ordinal_injective
|
||||
succ?_toFun := by simp [succ?_eq]
|
||||
succMany?_toFun := by simp [succMany?_eq]
|
||||
|
||||
@[simp]
|
||||
theorem toFun_map : map.toFun = Char.ordinal := rfl
|
||||
|
||||
instance : Map.PreservesLE map where
|
||||
le_iff := by simp [le_iff_ordinal_le]
|
||||
|
||||
instance : Map.PreservesRxcSize map where
|
||||
size_eq := rfl
|
||||
|
||||
instance : Map.PreservesRxoSize map where
|
||||
size_eq := rfl
|
||||
|
||||
instance : Map.PreservesRxiSize map where
|
||||
size_eq := rfl
|
||||
|
||||
instance : Map.PreservesLeast? map where
|
||||
map_least? := by simp
|
||||
|
||||
public instance : LawfulUpwardEnumerable Char := .ofMap map
|
||||
public instance : LawfulUpwardEnumerableLE Char := .ofMap map
|
||||
public instance : LawfulUpwardEnumerableLT Char := .ofMap map
|
||||
public instance : LawfulUpwardEnumerableLeast? Char := .ofMap map
|
||||
public instance : Rxc.LawfulHasSize Char := .ofMap map
|
||||
public instance : Rxc.IsAlwaysFinite Char := .ofMap map
|
||||
public instance : Rxo.LawfulHasSize Char := .ofMap map
|
||||
public instance : Rxo.IsAlwaysFinite Char := .ofMap map
|
||||
public instance : Rxi.LawfulHasSize Char := .ofMap map
|
||||
public instance : Rxi.IsAlwaysFinite Char := .ofMap map
|
||||
|
||||
end Char
|
||||
92
src/Init/Data/Range/Polymorphic/Fin.lean
Normal file
92
src/Init/Data/Range/Polymorphic/Fin.lean
Normal file
@@ -0,0 +1,92 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Range.Polymorphic.Instances
|
||||
public import Init.Data.Fin.OverflowAware
|
||||
import Init.Grind
|
||||
|
||||
public section
|
||||
|
||||
open Std Std.PRange
|
||||
|
||||
namespace Fin
|
||||
|
||||
instance : UpwardEnumerable (Fin n) where
|
||||
succ? i := i.addNat? 1
|
||||
succMany? m i := i.addNat? m
|
||||
|
||||
@[simp, grind =]
|
||||
theorem pRangeSucc?_eq : PRange.succ? (α := Fin n) = (·.addNat? 1) := rfl
|
||||
|
||||
@[simp, grind =]
|
||||
theorem pRangeSuccMany?_eq : PRange.succMany? m (α := Fin n) = (·.addNat? m) :=
|
||||
rfl
|
||||
|
||||
instance : LawfulUpwardEnumerable (Fin n) where
|
||||
ne_of_lt a b := by grind [UpwardEnumerable.LT]
|
||||
succMany?_zero a := by simp
|
||||
succMany?_add_one m a := by grind
|
||||
|
||||
instance : LawfulUpwardEnumerableLE (Fin n) where
|
||||
le_iff x y := by
|
||||
simp only [le_def, UpwardEnumerable.LE, pRangeSuccMany?_eq, Fin.addNat?_eq_dif,
|
||||
Option.dite_none_right_eq_some, Option.some.injEq, ← val_inj, exists_prop]
|
||||
exact ⟨fun h => ⟨y - x, by grind⟩, by grind⟩
|
||||
|
||||
instance : Least? (Fin 0) where
|
||||
least? := none
|
||||
|
||||
instance : LawfulUpwardEnumerableLeast? (Fin 0) where
|
||||
least?_le a := False.elim (Nat.not_lt_zero _ a.isLt)
|
||||
|
||||
@[simp]
|
||||
theorem least?_eq_of_zero : Least?.least? (α := Fin 0) = none := rfl
|
||||
|
||||
instance [NeZero n] : Least? (Fin n) where
|
||||
least? := some 0
|
||||
|
||||
instance [NeZero n] : LawfulUpwardEnumerableLeast? (Fin n) where
|
||||
least?_le a := ⟨0, rfl, (LawfulUpwardEnumerableLE.le_iff 0 a).1 (Fin.zero_le _)⟩
|
||||
|
||||
@[simp]
|
||||
theorem least?_eq [NeZero n] : Least?.least? (α := Fin n) = some 0 := rfl
|
||||
|
||||
instance : LawfulUpwardEnumerableLT (Fin n) := inferInstance
|
||||
|
||||
instance : Rxc.HasSize (Fin n) where
|
||||
size lo hi := hi + 1 - lo
|
||||
|
||||
@[grind =]
|
||||
theorem rxcHasSize_eq :
|
||||
Rxc.HasSize.size (α := Fin n) = fun (lo hi : Fin n) => (hi + 1 - lo : Nat) := rfl
|
||||
|
||||
instance : Rxc.LawfulHasSize (Fin n) where
|
||||
size_eq_zero_of_not_le bound x := by grind
|
||||
size_eq_one_of_succ?_eq_none lo hi := by grind
|
||||
size_eq_succ_of_succ?_eq_some lo hi x := by grind
|
||||
|
||||
instance : Rxc.IsAlwaysFinite (Fin n) := inferInstance
|
||||
|
||||
instance : Rxo.HasSize (Fin n) := .ofClosed
|
||||
instance : Rxo.LawfulHasSize (Fin n) := inferInstance
|
||||
instance : Rxo.IsAlwaysFinite (Fin n) := inferInstance
|
||||
|
||||
instance : Rxi.HasSize (Fin n) where
|
||||
size lo := n - lo
|
||||
|
||||
@[grind =]
|
||||
theorem rxiHasSize_eq :
|
||||
Rxi.HasSize.size (α := Fin n) = fun (lo : Fin n) => (n - lo : Nat) := rfl
|
||||
|
||||
instance : Rxi.LawfulHasSize (Fin n) where
|
||||
size_eq_one_of_succ?_eq_none x := by grind
|
||||
size_eq_succ_of_succ?_eq_some lo lo' := by grind
|
||||
|
||||
instance : Rxi.IsAlwaysFinite (Fin n) := inferInstance
|
||||
|
||||
end Fin
|
||||
195
src/Init/Data/Range/Polymorphic/Map.lean
Normal file
195
src/Init/Data/Range/Polymorphic/Map.lean
Normal file
@@ -0,0 +1,195 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Markus Himmel
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Range.Polymorphic.Instances
|
||||
public import Init.Data.Function
|
||||
import Init.Data.Order.Lemmas
|
||||
import Init.Data.Option.Function
|
||||
|
||||
public section
|
||||
|
||||
/-!
|
||||
# Mappings between `UpwardEnumerable` types
|
||||
|
||||
In this file we build machinery for pulling back lawfulness properties for `UpwardEnumerable` along
|
||||
injective functions that commute with the relevant operations.
|
||||
-/
|
||||
|
||||
namespace Std
|
||||
|
||||
namespace PRange
|
||||
|
||||
namespace UpwardEnumerable
|
||||
|
||||
/--
|
||||
An injective mapping between two types implementing `UpwardEnumerable` that commutes with `succ?`
|
||||
and `succMany?`.
|
||||
|
||||
Having such a mapping means that all of the `Prop`-valued lawfulness classes around
|
||||
`UpwardEnumerable` can be pulled back.
|
||||
-/
|
||||
structure Map (α : Type u) (β : Type v) [UpwardEnumerable α] [UpwardEnumerable β] where
|
||||
toFun : α → β
|
||||
injective : Function.Injective toFun
|
||||
succ?_toFun (a : α) : succ? (toFun a) = (succ? a).map toFun
|
||||
succMany?_toFun (n : Nat) (a : α) : succMany? n (toFun a) = (succMany? n a).map toFun
|
||||
|
||||
namespace Map
|
||||
|
||||
variable [UpwardEnumerable α] [UpwardEnumerable β]
|
||||
|
||||
theorem succ?_eq_none_iff (f : Map α β) {a : α} :
|
||||
succ? a = none ↔ succ? (f.toFun a) = none := by
|
||||
rw [← (Option.map_injective f.injective).eq_iff, Option.map_none, ← f.succ?_toFun]
|
||||
|
||||
theorem succ?_eq_some_iff (f : Map α β) {a b : α} :
|
||||
succ? a = some b ↔ succ? (f.toFun a) = some (f.toFun b) := by
|
||||
rw [← (Option.map_injective f.injective).eq_iff, Option.map_some, ← f.succ?_toFun]
|
||||
|
||||
theorem le_iff (f : Map α β) {a b : α} :
|
||||
UpwardEnumerable.LE a b ↔ UpwardEnumerable.LE (f.toFun a) (f.toFun b) := by
|
||||
simp only [UpwardEnumerable.LE, f.succMany?_toFun, Option.map_eq_some_iff]
|
||||
refine ⟨fun ⟨n, hn⟩ => ⟨n, b, by simp [hn]⟩, fun ⟨n, c, hn⟩ => ⟨n, ?_⟩⟩
|
||||
rw [hn.1, Option.some_inj, f.injective hn.2]
|
||||
|
||||
theorem lt_iff (f : Map α β) {a b : α} :
|
||||
UpwardEnumerable.LT a b ↔ UpwardEnumerable.LT (f.toFun a) (f.toFun b) := by
|
||||
simp only [UpwardEnumerable.LT, f.succMany?_toFun, Option.map_eq_some_iff]
|
||||
refine ⟨fun ⟨n, hn⟩ => ⟨n, b, by simp [hn]⟩, fun ⟨n, c, hn⟩ => ⟨n, ?_⟩⟩
|
||||
rw [hn.1, Option.some_inj, f.injective hn.2]
|
||||
|
||||
theorem succ?_toFun' (f : Map α β) : succ? ∘ f.toFun = Option.map f.toFun ∘ succ? := by
|
||||
ext
|
||||
simp [f.succ?_toFun]
|
||||
|
||||
/-- Compatibility class for `Map` and `≤`. -/
|
||||
class PreservesLE [LE α] [LE β] (f : Map α β) where
|
||||
le_iff : a ≤ b ↔ f.toFun a ≤ f.toFun b
|
||||
|
||||
/-- Compatibility class for `Map` and `<`. -/
|
||||
class PreservesLT [LT α] [LT β] (f : Map α β) where
|
||||
lt_iff : a < b ↔ f.toFun a < f.toFun b
|
||||
|
||||
/-- Compatibility class for `Map` and `Rxc.HasSize`. -/
|
||||
class PreservesRxcSize [Rxc.HasSize α] [Rxc.HasSize β] (f : Map α β) where
|
||||
size_eq : Rxc.HasSize.size a b = Rxc.HasSize.size (f.toFun a) (f.toFun b)
|
||||
|
||||
/-- Compatibility class for `Map` and `Rxo.HasSize`. -/
|
||||
class PreservesRxoSize [Rxo.HasSize α] [Rxo.HasSize β] (f : Map α β) where
|
||||
size_eq : Rxo.HasSize.size a b = Rxo.HasSize.size (f.toFun a) (f.toFun b)
|
||||
|
||||
/-- Compatibility class for `Map` and `Rxi.HasSize`. -/
|
||||
class PreservesRxiSize [Rxi.HasSize α] [Rxi.HasSize β] (f : Map α β) where
|
||||
size_eq : Rxi.HasSize.size b = Rxi.HasSize.size (f.toFun b)
|
||||
|
||||
/-- Compatibility class for `Map` and `Least?`. -/
|
||||
class PreservesLeast? [Least? α] [Least? β] (f : Map α β) where
|
||||
map_least? : Least?.least?.map f.toFun = Least?.least?
|
||||
|
||||
end UpwardEnumerable.Map
|
||||
|
||||
open UpwardEnumerable
|
||||
|
||||
variable [UpwardEnumerable α] [UpwardEnumerable β]
|
||||
|
||||
theorem LawfulUpwardEnumerable.ofMap [LawfulUpwardEnumerable β] (f : Map α β) :
|
||||
LawfulUpwardEnumerable α where
|
||||
ne_of_lt a b := by
|
||||
simpa only [f.lt_iff, ← f.injective.ne_iff] using LawfulUpwardEnumerable.ne_of_lt _ _
|
||||
succMany?_zero a := by
|
||||
apply Option.map_injective f.injective
|
||||
simpa [← f.succMany?_toFun] using LawfulUpwardEnumerable.succMany?_zero _
|
||||
succMany?_add_one n a := by
|
||||
apply Option.map_injective f.injective
|
||||
rw [← f.succMany?_toFun, LawfulUpwardEnumerable.succMany?_add_one,
|
||||
f.succMany?_toFun, Option.bind_map, Map.succ?_toFun', Option.map_bind]
|
||||
|
||||
instance [LE α] [LT α] [LawfulOrderLT α] [LE β] [LT β] [LawfulOrderLT β] (f : Map α β)
|
||||
[f.PreservesLE] : f.PreservesLT where
|
||||
lt_iff := by simp [lt_iff_le_and_not_ge, Map.PreservesLE.le_iff (f := f)]
|
||||
|
||||
theorem LawfulUpwardEnumerableLE.ofMap [LE α] [LE β] [LawfulUpwardEnumerableLE β] (f : Map α β)
|
||||
[f.PreservesLE] : LawfulUpwardEnumerableLE α where
|
||||
le_iff := by simp [Map.PreservesLE.le_iff (f := f), f.le_iff, LawfulUpwardEnumerableLE.le_iff]
|
||||
|
||||
theorem LawfulUpwardEnumerableLT.ofMap [LT α] [LT β] [LawfulUpwardEnumerableLT β] (f : Map α β)
|
||||
[f.PreservesLT] : LawfulUpwardEnumerableLT α where
|
||||
lt_iff := by simp [Map.PreservesLT.lt_iff (f := f), f.lt_iff, LawfulUpwardEnumerableLT.lt_iff]
|
||||
|
||||
theorem LawfulUpwardEnumerableLeast?.ofMap [Least? α] [Least? β] [LawfulUpwardEnumerableLeast? β]
|
||||
(f : Map α β) [f.PreservesLeast?] : LawfulUpwardEnumerableLeast? α where
|
||||
least?_le a := by
|
||||
obtain ⟨l, hl, hl'⟩ := LawfulUpwardEnumerableLeast?.least?_le (f.toFun a)
|
||||
have : (Least?.least? (α := α)).isSome := by
|
||||
rw [← Option.isSome_map (f := f.toFun), Map.PreservesLeast?.map_least?,
|
||||
hl, Option.isSome_some]
|
||||
refine ⟨Option.get _ this, by simp, ?_⟩
|
||||
rw [f.le_iff, Option.apply_get (f := f.toFun)]
|
||||
simpa [Map.PreservesLeast?.map_least?, hl] using hl'
|
||||
|
||||
end PRange
|
||||
|
||||
open PRange PRange.UpwardEnumerable
|
||||
|
||||
variable [UpwardEnumerable α] [UpwardEnumerable β]
|
||||
|
||||
theorem Rxc.LawfulHasSize.ofMap [LE α] [LE β] [Rxc.HasSize α] [Rxc.HasSize β] [Rxc.LawfulHasSize β]
|
||||
(f : Map α β) [f.PreservesLE] [f.PreservesRxcSize] : Rxc.LawfulHasSize α where
|
||||
size_eq_zero_of_not_le a b := by
|
||||
simpa [Map.PreservesRxcSize.size_eq (f := f), Map.PreservesLE.le_iff (f := f)] using
|
||||
Rxc.LawfulHasSize.size_eq_zero_of_not_le _ _
|
||||
size_eq_one_of_succ?_eq_none lo hi := by
|
||||
simpa [Map.PreservesRxcSize.size_eq (f := f), Map.PreservesLE.le_iff (f := f),
|
||||
f.succ?_eq_none_iff] using
|
||||
Rxc.LawfulHasSize.size_eq_one_of_succ?_eq_none _ _
|
||||
size_eq_succ_of_succ?_eq_some lo hi lo' := by
|
||||
simpa [Map.PreservesRxcSize.size_eq (f := f), Map.PreservesLE.le_iff (f := f),
|
||||
f.succ?_eq_some_iff] using
|
||||
Rxc.LawfulHasSize.size_eq_succ_of_succ?_eq_some _ _ _
|
||||
|
||||
theorem Rxo.LawfulHasSize.ofMap [LT α] [LT β] [Rxo.HasSize α] [Rxo.HasSize β] [Rxo.LawfulHasSize β]
|
||||
(f : Map α β) [f.PreservesLT] [f.PreservesRxoSize] : Rxo.LawfulHasSize α where
|
||||
size_eq_zero_of_not_le a b := by
|
||||
simpa [Map.PreservesRxoSize.size_eq (f := f), Map.PreservesLT.lt_iff (f := f)] using
|
||||
Rxo.LawfulHasSize.size_eq_zero_of_not_le _ _
|
||||
size_eq_one_of_succ?_eq_none lo hi := by
|
||||
simpa [Map.PreservesRxoSize.size_eq (f := f), Map.PreservesLT.lt_iff (f := f),
|
||||
f.succ?_eq_none_iff] using
|
||||
Rxo.LawfulHasSize.size_eq_one_of_succ?_eq_none _ _
|
||||
size_eq_succ_of_succ?_eq_some lo hi lo' := by
|
||||
simpa [Map.PreservesRxoSize.size_eq (f := f), Map.PreservesLT.lt_iff (f := f),
|
||||
f.succ?_eq_some_iff] using
|
||||
Rxo.LawfulHasSize.size_eq_succ_of_succ?_eq_some _ _ _
|
||||
|
||||
theorem Rxi.LawfulHasSize.ofMap [Rxi.HasSize α] [Rxi.HasSize β] [Rxi.LawfulHasSize β]
|
||||
(f : Map α β) [f.PreservesRxiSize] : Rxi.LawfulHasSize α where
|
||||
size_eq_one_of_succ?_eq_none lo := by
|
||||
simpa [Map.PreservesRxiSize.size_eq (f := f), f.succ?_eq_none_iff] using
|
||||
Rxi.LawfulHasSize.size_eq_one_of_succ?_eq_none _
|
||||
size_eq_succ_of_succ?_eq_some lo lo' := by
|
||||
simpa [Map.PreservesRxiSize.size_eq (f := f), f.succ?_eq_some_iff] using
|
||||
Rxi.LawfulHasSize.size_eq_succ_of_succ?_eq_some _ _
|
||||
|
||||
theorem Rxc.IsAlwaysFinite.ofMap [LE α] [LE β] [Rxc.IsAlwaysFinite β] (f : Map α β)
|
||||
[f.PreservesLE] : Rxc.IsAlwaysFinite α where
|
||||
finite init hi := by
|
||||
obtain ⟨n, hn⟩ := Rxc.IsAlwaysFinite.finite (f.toFun init) (f.toFun hi)
|
||||
exact ⟨n, by simpa [f.succMany?_toFun, Map.PreservesLE.le_iff (f := f)] using hn⟩
|
||||
|
||||
theorem Rxo.IsAlwaysFinite.ofMap [LT α] [LT β] [Rxo.IsAlwaysFinite β] (f : Map α β)
|
||||
[f.PreservesLT] : Rxo.IsAlwaysFinite α where
|
||||
finite init hi := by
|
||||
obtain ⟨n, hn⟩ := Rxo.IsAlwaysFinite.finite (f.toFun init) (f.toFun hi)
|
||||
exact ⟨n, by simpa [f.succMany?_toFun, Map.PreservesLT.lt_iff (f := f)] using hn⟩
|
||||
|
||||
theorem Rxi.IsAlwaysFinite.ofMap [Rxi.IsAlwaysFinite β] (f : Map α β) : Rxi.IsAlwaysFinite α where
|
||||
finite init := by
|
||||
obtain ⟨n, hn⟩ := Rxi.IsAlwaysFinite.finite (f.toFun init)
|
||||
exact ⟨n, by simpa [f.succMany?_toFun] using hn⟩
|
||||
|
||||
end Std
|
||||
@@ -157,7 +157,7 @@ Converts an 8-bit signed integer to a natural number, mapping all negative numbe
|
||||
|
||||
Use `Int8.toBitVec` to obtain the two's complement representation.
|
||||
-/
|
||||
@[inline] def Int8.toNatClampNeg (i : Int8) : Nat := i.toInt.toNat
|
||||
@[suggest_for Int8.toNat, inline] def Int8.toNatClampNeg (i : Int8) : Nat := i.toInt.toNat
|
||||
|
||||
/-- Obtains the `Int8` whose 2's complement representation is the given `BitVec 8`. -/
|
||||
@[inline] def Int8.ofBitVec (b : BitVec 8) : Int8 := ⟨⟨b⟩⟩
|
||||
@@ -510,7 +510,7 @@ Converts a 16-bit signed integer to a natural number, mapping all negative numbe
|
||||
|
||||
Use `Int16.toBitVec` to obtain the two's complement representation.
|
||||
-/
|
||||
@[inline] def Int16.toNatClampNeg (i : Int16) : Nat := i.toInt.toNat
|
||||
@[suggest_for Int16.toNat, inline] def Int16.toNatClampNeg (i : Int16) : Nat := i.toInt.toNat
|
||||
|
||||
/-- Obtains the `Int16` whose 2's complement representation is the given `BitVec 16`. -/
|
||||
@[inline] def Int16.ofBitVec (b : BitVec 16) : Int16 := ⟨⟨b⟩⟩
|
||||
@@ -880,7 +880,7 @@ Converts a 32-bit signed integer to a natural number, mapping all negative numbe
|
||||
|
||||
Use `Int32.toBitVec` to obtain the two's complement representation.
|
||||
-/
|
||||
@[inline] def Int32.toNatClampNeg (i : Int32) : Nat := i.toInt.toNat
|
||||
@[suggest_for Int32.toNat, inline] def Int32.toNatClampNeg (i : Int32) : Nat := i.toInt.toNat
|
||||
|
||||
/-- Obtains the `Int32` whose 2's complement representation is the given `BitVec 32`. -/
|
||||
@[inline] def Int32.ofBitVec (b : BitVec 32) : Int32 := ⟨⟨b⟩⟩
|
||||
@@ -1270,7 +1270,7 @@ Converts a 64-bit signed integer to a natural number, mapping all negative numbe
|
||||
|
||||
Use `Int64.toBitVec` to obtain the two's complement representation.
|
||||
-/
|
||||
@[inline] def Int64.toNatClampNeg (i : Int64) : Nat := i.toInt.toNat
|
||||
@[suggest_for Int64.toNat, inline] def Int64.toNatClampNeg (i : Int64) : Nat := i.toInt.toNat
|
||||
|
||||
/-- Obtains the `Int64` whose 2's complement representation is the given `BitVec 64`. -/
|
||||
@[inline] def Int64.ofBitVec (b : BitVec 64) : Int64 := ⟨⟨b⟩⟩
|
||||
@@ -1637,7 +1637,7 @@ Converts a word-sized signed integer to a natural number, mapping all negative n
|
||||
|
||||
Use `ISize.toBitVec` to obtain the two's complement representation.
|
||||
-/
|
||||
@[inline] def ISize.toNatClampNeg (i : ISize) : Nat := i.toInt.toNat
|
||||
@[suggest_for ISize.toNat, inline] def ISize.toNatClampNeg (i : ISize) : Nat := i.toInt.toNat
|
||||
|
||||
/-- Obtains the `ISize` whose 2's complement representation is the given `BitVec`. -/
|
||||
@[inline] def ISize.ofBitVec (b : BitVec System.Platform.numBits) : ISize := ⟨⟨b⟩⟩
|
||||
|
||||
@@ -4,12 +4,9 @@ Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Classical
|
||||
|
||||
public section
|
||||
|
||||
namespace Lean.Grind
|
||||
|
||||
/-- A helper gadget for annotating nested proofs in goals. -/
|
||||
|
||||
@@ -360,7 +360,7 @@ recommended_spelling "smul" for "•" in [HSMul.hSMul, «term_•_»]
|
||||
recommended_spelling "append" for "++" in [HAppend.hAppend, «term_++_»]
|
||||
/-- when used as a unary operator -/
|
||||
recommended_spelling "neg" for "-" in [Neg.neg, «term-_»]
|
||||
recommended_spelling "inv" for "⁻¹" in [Inv.inv]
|
||||
recommended_spelling "inv" for "⁻¹" in [Inv.inv, «term_⁻¹»]
|
||||
recommended_spelling "dvd" for "∣" in [Dvd.dvd, «term_∣_»]
|
||||
recommended_spelling "shiftLeft" for "<<<" in [HShiftLeft.hShiftLeft, «term_<<<_»]
|
||||
recommended_spelling "shiftRight" for ">>>" in [HShiftRight.hShiftRight, «term_>>>_»]
|
||||
|
||||
@@ -2810,6 +2810,8 @@ structure Char where
|
||||
/-- The value must be a legal scalar value. -/
|
||||
valid : val.isValidChar
|
||||
|
||||
grind_pattern Char.valid => self.val
|
||||
|
||||
private theorem isValidChar_UInt32 {n : Nat} (h : n.isValidChar) : LT.lt n UInt32.size :=
|
||||
match h with
|
||||
| Or.inl h => Nat.lt_trans h (of_decide_eq_true rfl)
|
||||
|
||||
@@ -44,6 +44,61 @@ theorem implies_congr_left {p₁ p₂ : Sort u} {q : Sort v} (h : p₁ = p₂) :
|
||||
theorem implies_congr_right {p : Sort u} {q₁ q₂ : Sort v} (h : q₁ = q₂) : (p → q₁) = (p → q₂) :=
|
||||
h ▸ rfl
|
||||
|
||||
namespace Lean
|
||||
/--
|
||||
`Arrow α β` is definitionally equal to `α → β`, but represented as a function
|
||||
application rather than `Expr.forallE`.
|
||||
|
||||
This representation is useful for proof automation that builds nested implications
|
||||
like `pₙ → ... → p₂ → p₁`. With `Expr.forallE`, each nesting level introduces a
|
||||
binder that bumps de Bruijn indices in subterms, destroying sharing even with
|
||||
hash-consing. For example, if `p₁` contains `#20`, then at depth 2 it becomes `#21`,
|
||||
at depth 3 it becomes `#22`, etc., causing quadratic proof growth.
|
||||
|
||||
With `arrow`, both arguments are explicit (not under binders), so subterms remain
|
||||
identical across nesting levels and can be shared, yielding linear-sized proofs.
|
||||
-/
|
||||
def Arrow (α : Sort u) (β : Sort v) : Sort (imax u v) := α → β
|
||||
|
||||
theorem arrow_congr {p₁ p₂ : Sort u} {q₁ q₂ : Sort v} (h₁ : p₁ = p₂) (h₂ : q₁ = q₂) : Arrow p₁ q₁ = Arrow p₂ q₂ :=
|
||||
h₁ ▸ h₂ ▸ rfl
|
||||
|
||||
theorem arrow_congr_left {p₁ p₂ : Sort u} {q : Sort v} (h : p₁ = p₂) : Arrow p₁ q = Arrow p₂ q :=
|
||||
h ▸ rfl
|
||||
|
||||
theorem arrow_congr_right {p : Sort u} {q₁ q₂ : Sort v} (h : q₁ = q₂) : Arrow p q₁ = Arrow p q₂ :=
|
||||
h ▸ rfl
|
||||
|
||||
theorem true_arrow (p : Prop) : Arrow True p = p := by
|
||||
simp [Arrow]; constructor
|
||||
next => intro h; exact h .intro
|
||||
next => intros; assumption
|
||||
|
||||
theorem true_arrow_congr_left (p q : Prop) : p = True → Arrow p q = q := by
|
||||
intros; subst p; apply true_arrow
|
||||
|
||||
theorem true_arrow_congr_right (q q' : Prop) : q = q' → Arrow True q = q' := by
|
||||
intros; subst q; apply true_arrow
|
||||
|
||||
theorem true_arrow_congr (p q q' : Prop) : p = True → q = q' → Arrow p q = q' := by
|
||||
intros; subst p q; apply true_arrow
|
||||
|
||||
theorem false_arrow (p : Prop) : Arrow False p = True := by
|
||||
simp [Arrow]; constructor
|
||||
next => intros; exact .intro
|
||||
next => intros; contradiction
|
||||
|
||||
theorem false_arrow_congr (p q : Prop) : p = False → Arrow p q = True := by
|
||||
intros; subst p; apply false_arrow
|
||||
|
||||
theorem arrow_true (α : Sort u) : Arrow α True = True := by
|
||||
simp [Arrow]; constructor <;> intros <;> exact .intro
|
||||
|
||||
theorem arrow_true_congr (α : Sort u) (p : Prop) : p = True → Arrow α p = True := by
|
||||
intros; subst p; apply arrow_true
|
||||
|
||||
end Lean
|
||||
|
||||
theorem iff_congr {p₁ p₂ q₁ q₂ : Prop} (h₁ : p₁ ↔ p₂) (h₂ : q₁ ↔ q₂) : (p₁ ↔ q₁) ↔ (p₂ ↔ q₂) :=
|
||||
Iff.of_eq (propext h₁ ▸ propext h₂ ▸ rfl)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ public section
|
||||
namespace Lean.Sym
|
||||
|
||||
theorem ne_self (a : α) : (a ≠ a) = False := by simp
|
||||
theorem not_true_eq : (¬ True) = False := by simp
|
||||
theorem not_false_eq : (¬ False) = True := by simp
|
||||
|
||||
theorem ite_cond_congr {α : Sort u} (c : Prop) {inst : Decidable c} (a b : α)
|
||||
(c' : Prop) {inst' : Decidable c'} (h : c = c') : @ite α c inst a b = @ite α c' inst' a b := by
|
||||
@@ -46,6 +48,8 @@ theorem UInt32.lt_eq_true (a b : UInt32) (h : decide (a < b) = true) : (a < b) =
|
||||
theorem UInt64.lt_eq_true (a b : UInt64) (h : decide (a < b) = true) : (a < b) = True := by simp_all
|
||||
theorem Fin.lt_eq_true (a b : Fin n) (h : decide (a < b) = true) : (a < b) = True := by simp_all
|
||||
theorem BitVec.lt_eq_true (a b : BitVec n) (h : decide (a < b) = true) : (a < b) = True := by simp_all
|
||||
theorem String.lt_eq_true (a b : String) (h : decide (a < b) = true) : (a < b) = True := by simp_all
|
||||
theorem Char.lt_eq_true (a b : Char) (h : decide (a < b) = true) : (a < b) = True := by simp_all
|
||||
|
||||
theorem Nat.lt_eq_false (a b : Nat) (h : decide (a < b) = false) : (a < b) = False := by simp_all
|
||||
theorem Int.lt_eq_false (a b : Int) (h : decide (a < b) = false) : (a < b) = False := by simp_all
|
||||
@@ -60,6 +64,8 @@ theorem UInt32.lt_eq_false (a b : UInt32) (h : decide (a < b) = false) : (a < b)
|
||||
theorem UInt64.lt_eq_false (a b : UInt64) (h : decide (a < b) = false) : (a < b) = False := by simp_all
|
||||
theorem Fin.lt_eq_false (a b : Fin n) (h : decide (a < b) = false) : (a < b) = False := by simp_all
|
||||
theorem BitVec.lt_eq_false (a b : BitVec n) (h : decide (a < b) = false) : (a < b) = False := by simp_all
|
||||
theorem String.lt_eq_false (a b : String) (h : decide (a < b) = false) : (a < b) = False := by simp_all
|
||||
theorem Char.lt_eq_false (a b : Char) (h : decide (a < b) = false) : (a < b) = False := by simp_all
|
||||
|
||||
theorem Nat.le_eq_true (a b : Nat) (h : decide (a ≤ b) = true) : (a ≤ b) = True := by simp_all
|
||||
theorem Int.le_eq_true (a b : Int) (h : decide (a ≤ b) = true) : (a ≤ b) = True := by simp_all
|
||||
@@ -74,6 +80,8 @@ theorem UInt32.le_eq_true (a b : UInt32) (h : decide (a ≤ b) = true) : (a ≤
|
||||
theorem UInt64.le_eq_true (a b : UInt64) (h : decide (a ≤ b) = true) : (a ≤ b) = True := by simp_all
|
||||
theorem Fin.le_eq_true (a b : Fin n) (h : decide (a ≤ b) = true) : (a ≤ b) = True := by simp_all
|
||||
theorem BitVec.le_eq_true (a b : BitVec n) (h : decide (a ≤ b) = true) : (a ≤ b) = True := by simp_all
|
||||
theorem String.le_eq_true (a b : String) (h : decide (a ≤ b) = true) : (a ≤ b) = True := by simp_all
|
||||
theorem Char.le_eq_true (a b : Char) (h : decide (a ≤ b) = true) : (a ≤ b) = True := by simp_all
|
||||
|
||||
theorem Nat.le_eq_false (a b : Nat) (h : decide (a ≤ b) = false) : (a ≤ b) = False := by simp_all
|
||||
theorem Int.le_eq_false (a b : Int) (h : decide (a ≤ b) = false) : (a ≤ b) = False := by simp_all
|
||||
@@ -88,62 +96,8 @@ theorem UInt32.le_eq_false (a b : UInt32) (h : decide (a ≤ b) = false) : (a
|
||||
theorem UInt64.le_eq_false (a b : UInt64) (h : decide (a ≤ b) = false) : (a ≤ b) = False := by simp_all
|
||||
theorem Fin.le_eq_false (a b : Fin n) (h : decide (a ≤ b) = false) : (a ≤ b) = False := by simp_all
|
||||
theorem BitVec.le_eq_false (a b : BitVec n) (h : decide (a ≤ b) = false) : (a ≤ b) = False := by simp_all
|
||||
|
||||
theorem Nat.gt_eq_true (a b : Nat) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
theorem Int.gt_eq_true (a b : Int) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
theorem Rat.gt_eq_true (a b : Rat) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
theorem Int8.gt_eq_true (a b : Int8) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
theorem Int16.gt_eq_true (a b : Int16) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
theorem Int32.gt_eq_true (a b : Int32) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
theorem Int64.gt_eq_true (a b : Int64) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
theorem UInt8.gt_eq_true (a b : UInt8) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
theorem UInt16.gt_eq_true (a b : UInt16) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
theorem UInt32.gt_eq_true (a b : UInt32) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
theorem UInt64.gt_eq_true (a b : UInt64) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
theorem Fin.gt_eq_true (a b : Fin n) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
theorem BitVec.gt_eq_true (a b : BitVec n) (h : decide (a > b) = true) : (a > b) = True := by simp_all
|
||||
|
||||
theorem Nat.gt_eq_false (a b : Nat) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
theorem Int.gt_eq_false (a b : Int) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
theorem Rat.gt_eq_false (a b : Rat) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
theorem Int8.gt_eq_false (a b : Int8) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
theorem Int16.gt_eq_false (a b : Int16) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
theorem Int32.gt_eq_false (a b : Int32) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
theorem Int64.gt_eq_false (a b : Int64) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
theorem UInt8.gt_eq_false (a b : UInt8) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
theorem UInt16.gt_eq_false (a b : UInt16) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
theorem UInt32.gt_eq_false (a b : UInt32) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
theorem UInt64.gt_eq_false (a b : UInt64) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
theorem Fin.gt_eq_false (a b : Fin n) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
theorem BitVec.gt_eq_false (a b : BitVec n) (h : decide (a > b) = false) : (a > b) = False := by simp_all
|
||||
|
||||
theorem Nat.ge_eq_true (a b : Nat) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
theorem Int.ge_eq_true (a b : Int) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
theorem Rat.ge_eq_true (a b : Rat) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
theorem Int8.ge_eq_true (a b : Int8) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
theorem Int16.ge_eq_true (a b : Int16) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
theorem Int32.ge_eq_true (a b : Int32) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
theorem Int64.ge_eq_true (a b : Int64) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
theorem UInt8.ge_eq_true (a b : UInt8) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
theorem UInt16.ge_eq_true (a b : UInt16) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
theorem UInt32.ge_eq_true (a b : UInt32) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
theorem UInt64.ge_eq_true (a b : UInt64) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
theorem Fin.ge_eq_true (a b : Fin n) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
theorem BitVec.ge_eq_true (a b : BitVec n) (h : decide (a ≥ b) = true) : (a ≥ b) = True := by simp_all
|
||||
|
||||
theorem Nat.ge_eq_false (a b : Nat) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem Int.ge_eq_false (a b : Int) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem Rat.ge_eq_false (a b : Rat) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem Int8.ge_eq_false (a b : Int8) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem Int16.ge_eq_false (a b : Int16) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem Int32.ge_eq_false (a b : Int32) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem Int64.ge_eq_false (a b : Int64) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem UInt8.ge_eq_false (a b : UInt8) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem UInt16.ge_eq_false (a b : UInt16) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem UInt32.ge_eq_false (a b : UInt32) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem UInt64.ge_eq_false (a b : UInt64) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem Fin.ge_eq_false (a b : Fin n) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem BitVec.ge_eq_false (a b : BitVec n) (h : decide (a ≥ b) = false) : (a ≥ b) = False := by simp_all
|
||||
theorem String.le_eq_false (a b : String) (h : decide (a ≤ b) = false) : (a ≤ b) = False := by simp_all
|
||||
theorem Char.le_eq_false (a b : Char) (h : decide (a ≤ b) = false) : (a ≤ b) = False := by simp_all
|
||||
|
||||
theorem Nat.eq_eq_true (a b : Nat) (h : decide (a = b) = true) : (a = b) = True := by simp_all
|
||||
theorem Int.eq_eq_true (a b : Int) (h : decide (a = b) = true) : (a = b) = True := by simp_all
|
||||
@@ -158,6 +112,8 @@ theorem UInt32.eq_eq_true (a b : UInt32) (h : decide (a = b) = true) : (a = b) =
|
||||
theorem UInt64.eq_eq_true (a b : UInt64) (h : decide (a = b) = true) : (a = b) = True := by simp_all
|
||||
theorem Fin.eq_eq_true (a b : Fin n) (h : decide (a = b) = true) : (a = b) = True := by simp_all
|
||||
theorem BitVec.eq_eq_true (a b : BitVec n) (h : decide (a = b) = true) : (a = b) = True := by simp_all
|
||||
theorem String.eq_eq_true (a b : String) (h : decide (a = b) = true) : (a = b) = True := by simp_all
|
||||
theorem Char.eq_eq_true (a b : Char) (h : decide (a = b) = true) : (a = b) = True := by simp_all
|
||||
|
||||
theorem Nat.eq_eq_false (a b : Nat) (h : decide (a = b) = false) : (a = b) = False := by simp_all
|
||||
theorem Int.eq_eq_false (a b : Int) (h : decide (a = b) = false) : (a = b) = False := by simp_all
|
||||
@@ -172,34 +128,8 @@ theorem UInt32.eq_eq_false (a b : UInt32) (h : decide (a = b) = false) : (a = b)
|
||||
theorem UInt64.eq_eq_false (a b : UInt64) (h : decide (a = b) = false) : (a = b) = False := by simp_all
|
||||
theorem Fin.eq_eq_false (a b : Fin n) (h : decide (a = b) = false) : (a = b) = False := by simp_all
|
||||
theorem BitVec.eq_eq_false (a b : BitVec n) (h : decide (a = b) = false) : (a = b) = False := by simp_all
|
||||
|
||||
theorem Nat.ne_eq_true (a b : Nat) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
theorem Int.ne_eq_true (a b : Int) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
theorem Rat.ne_eq_true (a b : Rat) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
theorem Int8.ne_eq_true (a b : Int8) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
theorem Int16.ne_eq_true (a b : Int16) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
theorem Int32.ne_eq_true (a b : Int32) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
theorem Int64.ne_eq_true (a b : Int64) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
theorem UInt8.ne_eq_true (a b : UInt8) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
theorem UInt16.ne_eq_true (a b : UInt16) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
theorem UInt32.ne_eq_true (a b : UInt32) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
theorem UInt64.ne_eq_true (a b : UInt64) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
theorem Fin.ne_eq_true (a b : Fin n) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
theorem BitVec.ne_eq_true (a b : BitVec n) (h : decide (a ≠ b) = true) : (a ≠ b) = True := by simp_all
|
||||
|
||||
theorem Nat.ne_eq_false (a b : Nat) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem Int.ne_eq_false (a b : Int) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem Rat.ne_eq_false (a b : Rat) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem Int8.ne_eq_false (a b : Int8) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem Int16.ne_eq_false (a b : Int16) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem Int32.ne_eq_false (a b : Int32) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem Int64.ne_eq_false (a b : Int64) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem UInt8.ne_eq_false (a b : UInt8) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem UInt16.ne_eq_false (a b : UInt16) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem UInt32.ne_eq_false (a b : UInt32) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem UInt64.ne_eq_false (a b : UInt64) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem Fin.ne_eq_false (a b : Fin n) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem BitVec.ne_eq_false (a b : BitVec n) (h : decide (a ≠ b) = false) : (a ≠ b) = False := by simp_all
|
||||
theorem String.eq_eq_false (a b : String) (h : decide (a = b) = false) : (a = b) = False := by simp_all
|
||||
theorem Char.eq_eq_false (a b : Char) (h : decide (a = b) = false) : (a = b) = False := by simp_all
|
||||
|
||||
theorem Nat.dvd_eq_true (a b : Nat) (h : decide (a ∣ b) = true) : (a ∣ b) = True := by simp_all
|
||||
theorem Int.dvd_eq_true (a b : Int) (h : decide (a ∣ b) = true) : (a ∣ b) = True := by simp_all
|
||||
|
||||
@@ -518,14 +518,13 @@ syntax location := withPosition(ppGroup(" at" (locationWildcard <|> locationHyp)
|
||||
assuming these are definitionally equal.
|
||||
* `change t' at h` will change hypothesis `h : t` to have type `t'`, assuming
|
||||
assuming `t` and `t'` are definitionally equal.
|
||||
-/
|
||||
syntax (name := change) "change " term (location)? : tactic
|
||||
|
||||
/--
|
||||
* `change a with b` will change occurrences of `a` to `b` in the goal,
|
||||
assuming `a` and `b` are definitionally equal.
|
||||
* `change a with b at h` similarly changes `a` to `b` in the type of hypothesis `h`.
|
||||
-/
|
||||
syntax (name := change) "change " term (location)? : tactic
|
||||
|
||||
@[tactic_alt change]
|
||||
syntax (name := changeWith) "change " term " with " term (location)? : tactic
|
||||
|
||||
/--
|
||||
@@ -905,8 +904,13 @@ The tactic supports all the same syntax variants and options as the `let` term.
|
||||
-/
|
||||
macro "let" c:letConfig d:letDecl : tactic => `(tactic| refine_lift let $c:letConfig $d:letDecl; ?_)
|
||||
|
||||
/-- `let rec f : t := e` adds a recursive definition `f` to the current goal.
|
||||
The syntax is the same as term-mode `let rec`. -/
|
||||
/--
|
||||
`let rec f : t := e` adds a recursive definition `f` to the current goal.
|
||||
The syntax is the same as term-mode `let rec`.
|
||||
|
||||
The tactic supports all the same syntax variants and options as the `let` term.
|
||||
-/
|
||||
@[tactic_name "let rec"]
|
||||
syntax (name := letrec) withPosition(atomic("let " &"rec ") letRecDecls) : tactic
|
||||
macro_rules
|
||||
| `(tactic| let rec $d) => `(tactic| refine_lift let rec $d; ?_)
|
||||
@@ -1212,22 +1216,6 @@ while `congr 2` produces the intended `⊢ x + y = y + x`.
|
||||
syntax (name := congr) "congr" (ppSpace num)? : tactic
|
||||
|
||||
|
||||
/--
|
||||
In tactic mode, `if h : t then tac1 else tac2` can be used as alternative syntax for:
|
||||
```
|
||||
by_cases h : t
|
||||
· tac1
|
||||
· tac2
|
||||
```
|
||||
It performs case distinction on `h : t` or `h : ¬t` and `tac1` and `tac2` are the subproofs.
|
||||
|
||||
You can use `?_` or `_` for either subproof to delay the goal to after the tactic, but
|
||||
if a tactic sequence is provided for `tac1` or `tac2` then it will require the goal to be closed
|
||||
by the end of the block.
|
||||
-/
|
||||
syntax (name := tacDepIfThenElse)
|
||||
ppRealGroup(ppRealFill(ppIndent("if " binderIdent " : " term " then") ppSpace matchRhsTacticSeq)
|
||||
ppDedent(ppSpace) ppRealFill("else " matchRhsTacticSeq)) : tactic
|
||||
|
||||
/--
|
||||
In tactic mode, `if t then tac1 else tac2` is alternative syntax for:
|
||||
@@ -1236,16 +1224,34 @@ by_cases t
|
||||
· tac1
|
||||
· tac2
|
||||
```
|
||||
It performs case distinction on `h† : t` or `h† : ¬t`, where `h†` is an anonymous
|
||||
hypothesis, and `tac1` and `tac2` are the subproofs. (It doesn't actually use
|
||||
nondependent `if`, since this wouldn't add anything to the context and hence would be
|
||||
useless for proving theorems. To actually insert an `ite` application use
|
||||
`refine if t then ?_ else ?_`.)
|
||||
It performs case distinction on `h† : t` or `h† : ¬t`, where `h†` is an anonymous hypothesis, and
|
||||
`tac1` and `tac2` are the subproofs. (It doesn't actually use nondependent `if`, since this wouldn't
|
||||
add anything to the context and hence would be useless for proving theorems. To actually insert an
|
||||
`ite` application use `refine if t then ?_ else ?_`.)
|
||||
|
||||
The assumptions in each subgoal can be named. `if h : t then tac1 else tac2` can be used as
|
||||
alternative syntax for:
|
||||
```
|
||||
by_cases h : t
|
||||
· tac1
|
||||
· tac2
|
||||
```
|
||||
It performs case distinction on `h : t` or `h : ¬t`.
|
||||
|
||||
You can use `?_` or `_` for either subproof to delay the goal to after the tactic, but
|
||||
if a tactic sequence is provided for `tac1` or `tac2` then it will require the goal to be closed
|
||||
by the end of the block.
|
||||
-/
|
||||
syntax (name := tacIfThenElse)
|
||||
ppRealGroup(ppRealFill(ppIndent("if " term " then") ppSpace matchRhsTacticSeq)
|
||||
ppDedent(ppSpace) ppRealFill("else " matchRhsTacticSeq)) : tactic
|
||||
|
||||
|
||||
@[tactic_alt tacIfThenElse]
|
||||
syntax (name := tacDepIfThenElse)
|
||||
ppRealGroup(ppRealFill(ppIndent("if " binderIdent " : " term " then") ppSpace matchRhsTacticSeq)
|
||||
ppDedent(ppSpace) ppRealFill("else " matchRhsTacticSeq)) : tactic
|
||||
|
||||
/--
|
||||
The tactic `nofun` is shorthand for `exact nofun`: it introduces the assumptions, then performs an
|
||||
empty pattern match, closing the goal if the introduced pattern is impossible.
|
||||
|
||||
@@ -27,6 +27,7 @@ public import Lean.Compiler.IR.ToIR
|
||||
public import Lean.Compiler.IR.ToIRType
|
||||
public import Lean.Compiler.IR.Meta
|
||||
public import Lean.Compiler.IR.Toposort
|
||||
public import Lean.Compiler.IR.SimpleGroundExpr
|
||||
|
||||
-- The following imports are not required by the compiler. They are here to ensure that there
|
||||
-- are no orphaned modules.
|
||||
@@ -71,6 +72,7 @@ def compile (decls : Array Decl) : CompilerM (Array Decl) := do
|
||||
logDecls `result decls
|
||||
checkDecls decls
|
||||
decls ← toposortDecls decls
|
||||
decls.forM Decl.detectSimpleGround
|
||||
addDecls decls
|
||||
inferMeta decls
|
||||
return decls
|
||||
|
||||
@@ -186,7 +186,7 @@ def getDecl (n : Name) : CompilerM Decl := do
|
||||
def findLocalDecl (n : Name) : CompilerM (Option Decl) :=
|
||||
return declMapExt.getState (← getEnv) |>.find? n
|
||||
|
||||
/-- Returns the list of IR declarations in declaration order. -/
|
||||
/-- Returns the list of IR declarations in reverse declaration order. -/
|
||||
def getDecls (env : Environment) : List Decl :=
|
||||
declMapExt.getEntries env
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ public import Lean.Compiler.IR.NormIds
|
||||
public import Lean.Compiler.IR.SimpCase
|
||||
public import Lean.Compiler.IR.Boxing
|
||||
public import Lean.Compiler.ModPkgExt
|
||||
import Lean.Compiler.IR.SimpleGroundExpr
|
||||
|
||||
public section
|
||||
|
||||
@@ -76,6 +77,26 @@ def toCType : IRType → String
|
||||
| IRType.struct _ _ => panic! "not implemented yet"
|
||||
| IRType.union _ _ => panic! "not implemented yet"
|
||||
|
||||
def toHexDigit (c : Nat) : String :=
|
||||
String.singleton c.digitChar
|
||||
|
||||
def quoteString (s : String) : String :=
|
||||
let q := "\"";
|
||||
let q := s.foldl
|
||||
(fun q c => q ++
|
||||
if c == '\n' then "\\n"
|
||||
else if c == '\r' then "\\r"
|
||||
else if c == '\t' then "\\t"
|
||||
else if c == '\\' then "\\\\"
|
||||
else if c == '\"' then "\\\""
|
||||
else if c == '?' then "\\?" -- avoid trigraphs
|
||||
else if c.toNat <= 31 then
|
||||
"\\x" ++ toHexDigit (c.toNat / 16) ++ toHexDigit (c.toNat % 16)
|
||||
-- TODO(Leo): we should use `\unnnn` for escaping unicode characters.
|
||||
else String.singleton c)
|
||||
q;
|
||||
q ++ "\""
|
||||
|
||||
def throwInvalidExportName {α : Type} (n : Name) : M α :=
|
||||
throw s!"invalid export name '{n}'"
|
||||
|
||||
@@ -101,30 +122,160 @@ def toCInitName (n : Name) : M String := do
|
||||
def emitCInitName (n : Name) : M Unit :=
|
||||
toCInitName n >>= emit
|
||||
|
||||
def ctorScalarSizeStr (usize : Nat) (ssize : Nat) : String :=
|
||||
if usize == 0 then toString ssize
|
||||
else if ssize == 0 then s!"sizeof(size_t)*{usize}"
|
||||
else s!"sizeof(size_t)*{usize} + {ssize}"
|
||||
|
||||
structure GroundState where
|
||||
auxCounter : Nat := 0
|
||||
|
||||
abbrev GroundM := StateT GroundState M
|
||||
|
||||
partial def emitGroundDecl (decl : Decl) (cppBaseName : String) : M Unit := do
|
||||
let some ground := getSimpleGroundExpr (← getEnv) decl.name | unreachable!
|
||||
discard <| compileGround ground |>.run {}
|
||||
where
|
||||
compileGround (e : SimpleGroundExpr) : GroundM Unit := do
|
||||
let valueName ← compileGroundToValue e
|
||||
let declPrefix := if isClosedTermName (← getEnv) decl.name then "static" else "LEAN_EXPORT"
|
||||
emitLn <| s!"{declPrefix} const lean_object* {cppBaseName} = (const lean_object*)&{valueName};"
|
||||
|
||||
compileGroundToValue (e : SimpleGroundExpr) : GroundM String := do
|
||||
match e with
|
||||
| .ctor cidx objArgs usizeArgs scalarArgs =>
|
||||
let val ← compileCtor cidx objArgs usizeArgs scalarArgs
|
||||
mkValueCLit "lean_ctor_object" val
|
||||
| .string data =>
|
||||
let leanStringTag := 249
|
||||
let header := mkHeader 0 0 leanStringTag
|
||||
let size := data.utf8ByteSize + 1 -- null byte
|
||||
let length := data.length
|
||||
let data : String := quoteString data
|
||||
mkValueCLit
|
||||
"lean_string_object"
|
||||
s!"\{.m_header = {header}, .m_size = {size}, .m_capacity = {size}, .m_length = {length}, .m_data = {data}}"
|
||||
| .pap func args =>
|
||||
let numFixed := args.size
|
||||
let leanClosureTag := 245
|
||||
let header := mkHeader s!"sizeof(lean_closure_object) + sizeof(void*)*{numFixed}" 0 leanClosureTag
|
||||
let funPtr := s!"(void*){← toCName func}"
|
||||
let arity := (← getDecl func).params.size
|
||||
let args ← args.mapM groundArgToCLit
|
||||
let argArray := String.intercalate "," args.toList
|
||||
mkValueCLit
|
||||
"lean_closure_object"
|
||||
s!"\{.m_header = {header}, .m_fun = {funPtr}, .m_arity = {arity}, .m_num_fixed = {numFixed}, .m_objs = \{{argArray}} }"
|
||||
| .nameMkStr args =>
|
||||
let obj ← groundNameMkStrToCLit args
|
||||
mkValueCLit "lean_ctor_object" obj
|
||||
| .reference refDecl => findValueDecl refDecl
|
||||
|
||||
mkValueName (name : String) : String :=
|
||||
name ++ "_value"
|
||||
|
||||
mkAuxValueName (name : String) (idx : Nat) : String :=
|
||||
mkValueName name ++ s!"_aux_{idx}"
|
||||
|
||||
mkAuxDecl (type value : String) : GroundM String := do
|
||||
let idx ← modifyGet fun s => (s.auxCounter, { s with auxCounter := s.auxCounter + 1 })
|
||||
let name := mkAuxValueName cppBaseName idx
|
||||
emitLn <| s!"static const {type} {name} = {value};"
|
||||
return name
|
||||
|
||||
mkValueCLit (type value : String) : GroundM String := do
|
||||
let valueName := mkValueName cppBaseName
|
||||
emitLn <| s!"static const {type} {valueName} = {value};"
|
||||
return valueName
|
||||
|
||||
groundNameMkStrToCLit (args : Array (Name × UInt64)) : GroundM String := do
|
||||
assert! args.size > 0
|
||||
if args.size == 1 then
|
||||
let (ref, hash) := args[0]!
|
||||
let hash := uint64ToByteArrayLE hash
|
||||
compileCtor 1 #[.tagged 0, .reference ref] #[] hash
|
||||
else
|
||||
let (ref, hash) := args.back!
|
||||
let args := args.pop
|
||||
let lit ← groundNameMkStrToCLit args
|
||||
let auxName ← mkAuxDecl "lean_ctor_object" lit
|
||||
let hash := uint64ToByteArrayLE hash
|
||||
compileCtor 1 #[.rawReference auxName, .reference ref] #[] hash
|
||||
|
||||
groundArgToCLit (a : SimpleGroundArg) : GroundM String := do
|
||||
match a with
|
||||
| .tagged val => return s!"((lean_object*)(((size_t)({val}) << 1) | 1))"
|
||||
| .reference decl => return s!"((lean_object*)&{← findValueDecl decl})"
|
||||
| .rawReference decl => return s!"((lean_object*)&{decl})"
|
||||
|
||||
findValueDecl (decl : Name) : GroundM String := do
|
||||
let mut decl := decl
|
||||
while true do
|
||||
if let some (.reference ref) := getSimpleGroundExpr (← getEnv) decl then
|
||||
decl := ref
|
||||
else
|
||||
break
|
||||
return mkValueName (← toCName decl)
|
||||
|
||||
compileCtor (cidx : Nat) (objArgs : Array SimpleGroundArg) (usizeArgs : Array USize)
|
||||
(scalarArgs : Array UInt8) : GroundM String := do
|
||||
let header := mkCtorHeader objArgs.size usizeArgs.size scalarArgs.size cidx
|
||||
let objArgs ← objArgs.mapM groundArgToCLit
|
||||
let usizeArgs : Array String := usizeArgs.map fun val => s!"(lean_object*)(size_t)({val}ULL)"
|
||||
assert! scalarArgs.size % 8 == 0
|
||||
let scalarArgs : Array String := Id.run do
|
||||
let chunks := scalarArgs.size / 8
|
||||
let mut packed := Array.emptyWithCapacity chunks
|
||||
for idx in 0...chunks do
|
||||
let b1 := scalarArgs[idx * 8]!
|
||||
let b2 := scalarArgs[idx * 8 + 1]!
|
||||
let b3 := scalarArgs[idx * 8 + 2]!
|
||||
let b4 := scalarArgs[idx * 8 + 3]!
|
||||
let b5 := scalarArgs[idx * 8 + 4]!
|
||||
let b6 := scalarArgs[idx * 8 + 5]!
|
||||
let b7 := scalarArgs[idx * 8 + 6]!
|
||||
let b8 := scalarArgs[idx * 8 + 7]!
|
||||
let lit := s!"LEAN_SCALAR_PTR_LITERAL({b1}, {b2}, {b3}, {b4}, {b5}, {b6}, {b7}, {b8})"
|
||||
packed := packed.push lit
|
||||
return packed
|
||||
let argArray := String.intercalate "," (objArgs ++ usizeArgs ++ scalarArgs).toList
|
||||
return s!"\{.m_header = {header}, .m_objs = \{{argArray}}}"
|
||||
|
||||
mkCtorHeader (numObjs : Nat) (usize : Nat) (ssize : Nat) (tag : Nat) : String :=
|
||||
let size := s!"sizeof(lean_ctor_object) + sizeof(void*)*{numObjs} + {ctorScalarSizeStr usize ssize}"
|
||||
mkHeader size numObjs tag
|
||||
|
||||
mkHeader {α : Type} [ToString α] (csSz : α) (other : Nat) (tag : Nat) : String :=
|
||||
s!"\{.m_rc = 0, .m_cs_sz = {csSz}, .m_other = {other}, .m_tag = {tag}}"
|
||||
|
||||
def emitFnDeclAux (decl : Decl) (cppBaseName : String) (isExternal : Bool) : M Unit := do
|
||||
let ps := decl.params
|
||||
let env ← getEnv
|
||||
if ps.isEmpty then
|
||||
if isExternal then emit "extern "
|
||||
else if isClosedTermName env decl.name then emit "static "
|
||||
else emit "LEAN_EXPORT "
|
||||
|
||||
if isSimpleGroundDecl env decl.name then
|
||||
emitGroundDecl decl cppBaseName
|
||||
else
|
||||
if !isExternal then emit "LEAN_EXPORT "
|
||||
emit (toCType decl.resultType ++ " " ++ cppBaseName)
|
||||
unless ps.isEmpty do
|
||||
emit "("
|
||||
-- We omit void parameters, note that they are guaranteed not to occur in boxed functions
|
||||
let ps := ps.filter (fun p => !p.ty.isVoid)
|
||||
-- We omit erased parameters for extern constants
|
||||
let ps := if isExternC env decl.name then ps.filter (fun p => !p.ty.isErased) else ps
|
||||
if ps.size > closureMaxArgs && isBoxedName decl.name then
|
||||
emit "lean_object**"
|
||||
if ps.isEmpty then
|
||||
if isExternal then emit "extern "
|
||||
else if isClosedTermName env decl.name then emit "static "
|
||||
else emit "LEAN_EXPORT "
|
||||
else
|
||||
ps.size.forM fun i _ => do
|
||||
if i > 0 then emit ", "
|
||||
emit (toCType ps[i].ty)
|
||||
emit ")"
|
||||
emitLn ";"
|
||||
if !isExternal then emit "LEAN_EXPORT "
|
||||
emit (toCType decl.resultType ++ " " ++ cppBaseName)
|
||||
unless ps.isEmpty do
|
||||
emit "("
|
||||
-- We omit void parameters, note that they are guaranteed not to occur in boxed functions
|
||||
let ps := ps.filter (fun p => !p.ty.isVoid)
|
||||
-- We omit erased parameters for extern constants
|
||||
let ps := if isExternC env decl.name then ps.filter (fun p => !p.ty.isErased) else ps
|
||||
if ps.size > closureMaxArgs && isBoxedName decl.name then
|
||||
emit "lean_object**"
|
||||
else
|
||||
ps.size.forM fun i _ => do
|
||||
if i > 0 then emit ", "
|
||||
emit (toCType ps[i].ty)
|
||||
emit ")"
|
||||
emitLn ";"
|
||||
|
||||
def emitFnDecl (decl : Decl) (isExternal : Bool) : M Unit := do
|
||||
let cppBaseName ← toCName decl.name
|
||||
@@ -137,10 +288,9 @@ def emitExternDeclAux (decl : Decl) (cNameStr : String) : M Unit := do
|
||||
|
||||
def emitFnDecls : M Unit := do
|
||||
let env ← getEnv
|
||||
let decls := getDecls env
|
||||
let decls := getDecls env |>.reverse
|
||||
let modDecls : NameSet := decls.foldl (fun s d => s.insert d.name) {}
|
||||
let usedDecls : NameSet := decls.foldl (fun s d => collectUsedDecls env d (s.insert d.name)) {}
|
||||
let usedDecls := usedDecls.toList
|
||||
let usedDecls := collectUsedDecls env decls
|
||||
usedDecls.forM fun n => do
|
||||
let decl ← getDecl n;
|
||||
match getExternNameFor env `c decl.name with
|
||||
@@ -353,10 +503,8 @@ def emitArgs (ys : Array Arg) : M Unit :=
|
||||
if i > 0 then emit ", "
|
||||
emitArg ys[i]
|
||||
|
||||
def emitCtorScalarSize (usize : Nat) (ssize : Nat) : M Unit := do
|
||||
if usize == 0 then emit ssize
|
||||
else if ssize == 0 then emit "sizeof(size_t)*"; emit usize
|
||||
else emit "sizeof(size_t)*"; emit usize; emit " + "; emit ssize
|
||||
def emitCtorScalarSize (usize : Nat) (ssize : Nat) : M Unit :=
|
||||
emit <| ctorScalarSizeStr usize ssize
|
||||
|
||||
def emitAllocCtor (c : CtorInfo) : M Unit := do
|
||||
emit "lean_alloc_ctor("; emit c.cidx; emit ", "; emit c.size; emit ", "
|
||||
@@ -435,12 +583,18 @@ def emitExternCall (f : FunId) (ps : Array Param) (extData : ExternAttrData) (ys
|
||||
| some (ExternEntry.inline _ pat) => do emit (expandExternPattern pat (toStringArgs ys)); emitLn ";"
|
||||
| _ => throw s!"failed to emit extern application '{f}'"
|
||||
|
||||
def emitLeanFunReference (f : FunId) : M Unit := do
|
||||
if isSimpleGroundDecl (← getEnv) f then
|
||||
emit s!"((lean_object*)({← toCName f}))"
|
||||
else
|
||||
emitCName f
|
||||
|
||||
def emitFullApp (z : VarId) (f : FunId) (ys : Array Arg) : M Unit := do
|
||||
emitLhs z
|
||||
let decl ← getDecl f
|
||||
match decl with
|
||||
| .fdecl (xs := ps) .. | .extern (xs := ps) (ext := { entries := [.opaque], .. }) .. =>
|
||||
emitCName f
|
||||
emitLeanFunReference f
|
||||
if ys.size > 0 then
|
||||
let (ys, _) := ys.zip ps |>.filter (fun (_, p) => !p.ty.isVoid) |>.unzip
|
||||
emit "("; emitArgs ys; emit ")"
|
||||
@@ -482,26 +636,6 @@ def emitUnbox (z : VarId) (t : IRType) (x : VarId) : M Unit := do
|
||||
def emitIsShared (z : VarId) (x : VarId) : M Unit := do
|
||||
emitLhs z; emit "!lean_is_exclusive("; emit x; emitLn ");"
|
||||
|
||||
def toHexDigit (c : Nat) : String :=
|
||||
String.singleton c.digitChar
|
||||
|
||||
def quoteString (s : String) : String :=
|
||||
let q := "\"";
|
||||
let q := s.foldl
|
||||
(fun q c => q ++
|
||||
if c == '\n' then "\\n"
|
||||
else if c == '\r' then "\\r"
|
||||
else if c == '\t' then "\\t"
|
||||
else if c == '\\' then "\\\\"
|
||||
else if c == '\"' then "\\\""
|
||||
else if c == '?' then "\\?" -- avoid trigraphs
|
||||
else if c.toNat <= 31 then
|
||||
"\\x" ++ toHexDigit (c.toNat / 16) ++ toHexDigit (c.toNat % 16)
|
||||
-- TODO(Leo): we should use `\unnnn` for escaping unicode characters.
|
||||
else String.singleton c)
|
||||
q;
|
||||
q ++ "\""
|
||||
|
||||
def emitNumLit (t : IRType) (v : Nat) : M Unit := do
|
||||
if t.isObj then
|
||||
if v < UInt32.size then
|
||||
@@ -670,7 +804,7 @@ def emitDeclAux (d : Decl) : M Unit := do
|
||||
let env ← getEnv
|
||||
let (_, jpMap) := mkVarJPMaps d
|
||||
withReader (fun ctx => { ctx with jpMap := jpMap }) do
|
||||
unless hasInitAttr env d.name do
|
||||
unless hasInitAttr env d.name || isSimpleGroundDecl env d.name do
|
||||
match d with
|
||||
| .fdecl (f := f) (xs := xs) (type := t) (body := b) .. =>
|
||||
let baseName ← toCName f;
|
||||
@@ -749,7 +883,8 @@ def emitDeclInit (d : Decl) : M Unit := do
|
||||
if getBuiltinInitFnNameFor? env d.name |>.isSome then
|
||||
emit "}"
|
||||
| _ =>
|
||||
emitCName n; emit " = "; emitCInitName n; emitLn "();"; emitMarkPersistent d n
|
||||
if !isSimpleGroundDecl env d.name then
|
||||
emitCName n; emit " = "; emitCInitName n; emitLn "();"; emitMarkPersistent d n
|
||||
|
||||
def emitInitFn : M Unit := do
|
||||
let env ← getEnv
|
||||
|
||||
@@ -31,6 +31,7 @@ time. These changes can likely be done similar to the ones in EmitC:
|
||||
- function decls need to be fixed
|
||||
- full applications need to be fixed
|
||||
- tail calls need to be fixed
|
||||
- closed term static initializers
|
||||
-/
|
||||
|
||||
def leanMainFn := "_lean_main"
|
||||
@@ -537,14 +538,12 @@ def emitFnDecls : M llvmctx Unit := do
|
||||
let env ← getEnv
|
||||
let decls := getDecls env
|
||||
let modDecls : NameSet := decls.foldl (fun s d => s.insert d.name) {}
|
||||
let usedDecls : NameSet := decls.foldl (fun s d => collectUsedDecls env d (s.insert d.name)) {}
|
||||
let usedDecls := usedDecls.toList
|
||||
for n in usedDecls do
|
||||
let decl ← getDecl n
|
||||
let usedDecls := collectUsedDecls env decls
|
||||
usedDecls.forM fun n => do
|
||||
let decl ← getDecl n;
|
||||
match getExternNameFor env `c decl.name with
|
||||
| some cName => emitExternDeclAux decl cName
|
||||
| none => emitFnDecl decl (!modDecls.contains n)
|
||||
return ()
|
||||
|
||||
def emitLhsSlot_ (x : VarId) : M llvmctx (LLVM.LLVMType llvmctx × LLVM.Value llvmctx) := do
|
||||
let state ← get
|
||||
|
||||
@@ -25,10 +25,19 @@ def usesModuleFrom (env : Environment) (modulePrefix : Name) : Bool :=
|
||||
|
||||
namespace CollectUsedDecls
|
||||
|
||||
abbrev M := ReaderT Environment (StateM NameSet)
|
||||
structure State where
|
||||
set : NameSet := {}
|
||||
order : Array Name := #[]
|
||||
|
||||
abbrev M := ReaderT Environment (StateM State)
|
||||
|
||||
@[inline] def collect (f : FunId) : M Unit :=
|
||||
modify fun s => s.insert f
|
||||
modify fun { set, order } =>
|
||||
let (contained, set) := set.containsThenInsert f
|
||||
if !contained then
|
||||
{ set, order := order.push f }
|
||||
else
|
||||
{ set, order }
|
||||
|
||||
partial def collectFnBody : FnBody → M Unit
|
||||
| .vdecl _ _ v b =>
|
||||
@@ -46,14 +55,19 @@ def collectInitDecl (fn : Name) : M Unit := do
|
||||
| some initFn => collect initFn
|
||||
| _ => pure ()
|
||||
|
||||
def collectDecl : Decl → M NameSet
|
||||
| .fdecl (f := f) (body := b) .. => collectInitDecl f *> CollectUsedDecls.collectFnBody b *> get
|
||||
| .extern (f := f) .. => collectInitDecl f *> get
|
||||
def collectDecl : Decl → M Unit
|
||||
| .fdecl (f := f) (body := b) .. => collectInitDecl f *> CollectUsedDecls.collectFnBody b
|
||||
| .extern (f := f) .. => collectInitDecl f
|
||||
|
||||
def collectDeclLoop (decls : List Decl) : M Unit := do
|
||||
decls.forM fun decl => do
|
||||
collectDecl decl
|
||||
collect decl.name
|
||||
|
||||
end CollectUsedDecls
|
||||
|
||||
def collectUsedDecls (env : Environment) (decl : Decl) (used : NameSet := {}) : NameSet :=
|
||||
(CollectUsedDecls.collectDecl decl env).run' used
|
||||
def collectUsedDecls (env : Environment) (decls : List Decl) : Array Name :=
|
||||
(CollectUsedDecls.collectDeclLoop decls env).run {} |>.snd.order
|
||||
|
||||
abbrev VarTypeMap := Std.HashMap VarId IRType
|
||||
abbrev JPParamsMap := Std.HashMap JoinPointId (Array Param)
|
||||
|
||||
355
src/Lean/Compiler/IR/SimpleGroundExpr.lean
Normal file
355
src/Lean/Compiler/IR/SimpleGroundExpr.lean
Normal file
@@ -0,0 +1,355 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Henrik Böving
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Lean.Compiler.IR.CompilerM
|
||||
public import Lean.EnvExtension
|
||||
import Lean.Compiler.ClosedTermCache
|
||||
|
||||
/-!
|
||||
This module contains logic for detecting simple ground expressions that can be extracted into
|
||||
statically initializable variables. To do this it attempts to compile declarations into
|
||||
a simple language of expressions, `SimpleGroundExpr`. If this attempt succeeds it stores the result
|
||||
in an environment extension, accessible through `getSimpleGroundExpr`. Later on the code emission
|
||||
step can reference this environment extension to generate static initializers for the respective
|
||||
declaration.
|
||||
-/
|
||||
|
||||
namespace Lean
|
||||
|
||||
namespace IR
|
||||
|
||||
/--
|
||||
An argument to a `SimpleGroundExpr`. They get compiled to `lean_object*` in various ways.
|
||||
-/
|
||||
public inductive SimpleGroundArg where
|
||||
/--
|
||||
A simple tagged literal.
|
||||
-/
|
||||
| tagged (val : Nat)
|
||||
/--
|
||||
A reference to another declaration that was marked as a simple ground expression. This gets
|
||||
compiled to a reference to the mangled version of the name.
|
||||
-/
|
||||
| reference (n : Name)
|
||||
/--
|
||||
A reference directly to a raw C name. This gets compiled to a reference to the name directly.
|
||||
-/
|
||||
| rawReference (s : String)
|
||||
deriving Inhabited
|
||||
|
||||
/--
|
||||
A simple ground expression that can be turned into a static initializer.
|
||||
-/
|
||||
public inductive SimpleGroundExpr where
|
||||
/--
|
||||
Represents a `lean_ctor_object`. Crucially the `scalarArgs` array must have a size that is a
|
||||
multiple of 8.
|
||||
-/
|
||||
| ctor (cidx : Nat) (objArgs : Array SimpleGroundArg) (usizeArgs : Array USize) (scalarArgs : Array UInt8)
|
||||
/--
|
||||
A string literal, represented by a `lean_string_object`.
|
||||
-/
|
||||
| string (data : String)
|
||||
/--
|
||||
A partial application, represented by a `lean_closure_object`.
|
||||
-/
|
||||
| pap (func : FunId) (args : Array SimpleGroundArg)
|
||||
/--
|
||||
An application of `Lean.Name.mkStrX`. This expression is represented separately to ensure that
|
||||
long name literals get extracted into statically initializable constants. The arguments contain
|
||||
both the name of the string literal it references as well as the hash of the name up to that
|
||||
point. This is done to make emitting the literal as simple as possible.
|
||||
-/
|
||||
| nameMkStr (args : Array (Name × UInt64))
|
||||
/--
|
||||
A reference to another declaration that was marked as a simple ground expression. This gets
|
||||
compiled to a reference to the mangled version of the name.
|
||||
-/
|
||||
| reference (n : Name)
|
||||
deriving Inhabited
|
||||
|
||||
public structure SimpleGroundExtState where
|
||||
constNames : PHashMap Name SimpleGroundExpr := {}
|
||||
revNames : List Name := []
|
||||
deriving Inhabited
|
||||
|
||||
builtin_initialize simpleGroundDeclExt : EnvExtension SimpleGroundExtState ←
|
||||
registerEnvExtension (pure {}) (asyncMode := .sync)
|
||||
(replay? := some fun oldState newState _ s =>
|
||||
let newNames := newState.revNames.take (newState.revNames.length - oldState.revNames.length)
|
||||
newNames.foldl (init := s) fun s n =>
|
||||
let g := newState.constNames.find! n
|
||||
{ s with constNames := s.constNames.insert n g, revNames := n :: s.revNames }
|
||||
)
|
||||
|
||||
/--
|
||||
Record `declName` as mapping to the simple ground expr `expr`.
|
||||
-/
|
||||
public def addSimpleGroundDecl (env : Environment) (declName : Name) (expr : SimpleGroundExpr) :
|
||||
Environment :=
|
||||
simpleGroundDeclExt.modifyState env fun s =>
|
||||
{ s with constNames := s.constNames.insert declName expr, revNames := declName :: s.revNames }
|
||||
|
||||
/--
|
||||
Attempt to fetch a `SimpleGroundExpr` associated with `declName` if it exists.
|
||||
-/
|
||||
public def getSimpleGroundExpr (env : Environment) (declName : Name) : Option SimpleGroundExpr :=
|
||||
(simpleGroundDeclExt.getState env).constNames.find? declName
|
||||
|
||||
/--
|
||||
Like `getSimpleGroundExpr` but recursively traverses `reference` exprs to get to actual ground
|
||||
values.
|
||||
-/
|
||||
public def getSimpleGroundExprWithResolvedRefs (env : Environment) (declName : Name) :
|
||||
Option SimpleGroundExpr := Id.run do
|
||||
let mut declName := declName
|
||||
while true do
|
||||
let val := getSimpleGroundExpr env declName
|
||||
match val with
|
||||
| some (.reference ref) => declName := ref
|
||||
| other => return other
|
||||
return none
|
||||
|
||||
/--
|
||||
Check if `declName` is recorded as being a `SimpleGroundExpr`.
|
||||
-/
|
||||
public def isSimpleGroundDecl (env : Environment) (declName : Name) : Bool :=
|
||||
(simpleGroundDeclExt.getState env).constNames.contains declName
|
||||
|
||||
public def uint64ToByteArrayLE (n : UInt64) : Array UInt8 :=
|
||||
#[
|
||||
n.toUInt8,
|
||||
(n >>> 0x08).toUInt8,
|
||||
(n >>> 0x10).toUInt8,
|
||||
(n >>> 0x18).toUInt8,
|
||||
(n >>> 0x20).toUInt8,
|
||||
(n >>> 0x28).toUInt8,
|
||||
(n >>> 0x30).toUInt8,
|
||||
(n >>> 0x38).toUInt8,
|
||||
]
|
||||
|
||||
|
||||
inductive SimpleGroundValue where
|
||||
| arg (arg : SimpleGroundArg)
|
||||
| uint8 (val : UInt8)
|
||||
| uint16 (val : UInt16)
|
||||
| uint32 (val : UInt32)
|
||||
| uint64 (val : UInt64)
|
||||
| usize (val : USize)
|
||||
deriving Inhabited
|
||||
|
||||
structure State where
|
||||
groundMap : Std.HashMap VarId SimpleGroundValue := {}
|
||||
|
||||
abbrev M := StateRefT State $ OptionT CompilerM
|
||||
|
||||
/--
|
||||
Attempt to compile `b` into a `SimpleGroundExpr`. If `b` is not compileable return `none`.
|
||||
|
||||
The compiler currently supports the following patterns:
|
||||
- String literals
|
||||
- Partial applications with other simple expressions
|
||||
- Constructor calls with other simple expressions
|
||||
- `Name.mkStrX`, `Name.str._override`, and `Name.num._override`
|
||||
- references to other declarations marked as simple ground expressions
|
||||
-/
|
||||
partial def compileToSimpleGroundExpr (b : FnBody) : CompilerM (Option SimpleGroundExpr) :=
|
||||
compileFnBody b |>.run' {} |>.run
|
||||
where
|
||||
compileFnBody (b : FnBody) : M SimpleGroundExpr := do
|
||||
match b with
|
||||
| .vdecl id _ expr (.ret (.var id')) =>
|
||||
guard <| id == id'
|
||||
compileFinalExpr expr
|
||||
| .vdecl id ty expr b => compileNonFinalExpr id ty expr b
|
||||
| _ => failure
|
||||
|
||||
@[inline]
|
||||
record (id : VarId) (val : SimpleGroundValue) : M Unit :=
|
||||
modify fun s => { s with groundMap := s.groundMap.insert id val }
|
||||
|
||||
compileNonFinalExpr (id : VarId) (ty : IRType) (expr : Expr) (b : FnBody) : M SimpleGroundExpr := do
|
||||
match expr with
|
||||
| .fap c #[] =>
|
||||
guard <| isSimpleGroundDecl (← getEnv) c
|
||||
record id (.arg (.reference c))
|
||||
compileFnBody b
|
||||
| .lit v =>
|
||||
match v with
|
||||
| .num v =>
|
||||
match ty with
|
||||
| .tagged =>
|
||||
guard <| v < 2^31
|
||||
record id (.arg (.tagged v))
|
||||
| .uint8 => record id (.uint8 (.ofNat v))
|
||||
| .uint16 => record id (.uint16 (.ofNat v))
|
||||
| .uint32 => record id (.uint32 (.ofNat v))
|
||||
| .uint64 => record id (.uint64 (.ofNat v))
|
||||
| .usize => record id (.usize (.ofNat v))
|
||||
| _ => failure
|
||||
compileFnBody b
|
||||
| .str .. => failure
|
||||
| .ctor i objArgs =>
|
||||
if i.isScalar then
|
||||
record id (.arg (.tagged i.cidx))
|
||||
compileFnBody b
|
||||
else
|
||||
let objArgs ← compileArgs objArgs
|
||||
let usizeArgs := Array.replicate i.usize 0
|
||||
-- Align to 8 bytes for alignment with lean_object*
|
||||
let align (v a : Nat) : Nat :=
|
||||
(v / a) * a + a * (if v % a != 0 then 1 else 0)
|
||||
let alignedSsize := align i.ssize 8
|
||||
let ssizeArgs := Array.replicate alignedSsize 0
|
||||
compileSetChain id i objArgs usizeArgs ssizeArgs b
|
||||
| _ => failure
|
||||
|
||||
compileSetChain (id : VarId) (info : CtorInfo) (objArgs : Array SimpleGroundArg) (usizeArgs : Array USize)
|
||||
(scalarArgs : Array UInt8) (b : FnBody) : M SimpleGroundExpr := do
|
||||
match b with
|
||||
| .ret (.var id') =>
|
||||
guard <| id == id'
|
||||
return .ctor info.cidx objArgs usizeArgs scalarArgs
|
||||
| .sset id' i offset y _ b =>
|
||||
guard <| id == id'
|
||||
let i := i - objArgs.size - usizeArgs.size
|
||||
let offset := i * 8 + offset
|
||||
let scalarArgs ←
|
||||
match (← get).groundMap[y]! with
|
||||
| .uint8 v =>
|
||||
let scalarArgs := scalarArgs.set! offset v
|
||||
pure scalarArgs
|
||||
| .uint16 v =>
|
||||
let scalarArgs := scalarArgs.set! offset v.toUInt8
|
||||
let scalarArgs := scalarArgs.set! (offset + 1) (v >>> 0x08).toUInt8
|
||||
pure scalarArgs
|
||||
| .uint32 v =>
|
||||
let scalarArgs := scalarArgs.set! offset v.toUInt8
|
||||
let scalarArgs := scalarArgs.set! (offset + 1) (v >>> 0x08).toUInt8
|
||||
let scalarArgs := scalarArgs.set! (offset + 2) (v >>> 0x10).toUInt8
|
||||
let scalarArgs := scalarArgs.set! (offset + 3) (v >>> 0x18).toUInt8
|
||||
pure scalarArgs
|
||||
| .uint64 v =>
|
||||
let scalarArgs := scalarArgs.set! offset v.toUInt8
|
||||
let scalarArgs := scalarArgs.set! (offset + 1) (v >>> 0x08).toUInt8
|
||||
let scalarArgs := scalarArgs.set! (offset + 2) (v >>> 0x10).toUInt8
|
||||
let scalarArgs := scalarArgs.set! (offset + 3) (v >>> 0x18).toUInt8
|
||||
let scalarArgs := scalarArgs.set! (offset + 4) (v >>> 0x20).toUInt8
|
||||
let scalarArgs := scalarArgs.set! (offset + 5) (v >>> 0x28).toUInt8
|
||||
let scalarArgs := scalarArgs.set! (offset + 6) (v >>> 0x30).toUInt8
|
||||
let scalarArgs := scalarArgs.set! (offset + 7) (v >>> 0x38).toUInt8
|
||||
pure scalarArgs
|
||||
| _ => failure
|
||||
compileSetChain id info objArgs usizeArgs scalarArgs b
|
||||
| .uset id' i y b =>
|
||||
guard <| id == id'
|
||||
let i := i - objArgs.size
|
||||
let .usize v := (← get).groundMap[y]! | failure
|
||||
let usizeArgs := usizeArgs.set! i v
|
||||
compileSetChain id info objArgs usizeArgs scalarArgs b
|
||||
| _ => failure
|
||||
|
||||
compileFinalExpr (e : Expr) : M SimpleGroundExpr := do
|
||||
match e with
|
||||
| .lit v =>
|
||||
match v with
|
||||
| .str v => return .string v
|
||||
| .num .. => failure
|
||||
| .ctor i args =>
|
||||
guard <| i.usize == 0 && i.ssize == 0 && !args.isEmpty
|
||||
return .ctor i.cidx (← compileArgs args) #[] #[]
|
||||
| .fap ``Name.num._override args =>
|
||||
let pre ← compileArg args[0]!
|
||||
let .tagged i ← compileArg args[1]! | failure
|
||||
let name := Name.num (← interpNameLiteral pre) i
|
||||
let hash := name.hash
|
||||
return .ctor 2 #[pre, .tagged i] #[] (uint64ToByteArrayLE hash)
|
||||
| .fap ``Name.str._override args =>
|
||||
let pre ← compileArg args[0]!
|
||||
let (ref, str) ← compileStrArg args[1]!
|
||||
let name := Name.str (← interpNameLiteral pre) str
|
||||
let hash := name.hash
|
||||
return .ctor 1 #[pre, .reference ref] #[] (uint64ToByteArrayLE hash)
|
||||
| .fap ``Name.mkStr1 args
|
||||
| .fap ``Name.mkStr2 args
|
||||
| .fap ``Name.mkStr3 args
|
||||
| .fap ``Name.mkStr4 args
|
||||
| .fap ``Name.mkStr5 args
|
||||
| .fap ``Name.mkStr6 args
|
||||
| .fap ``Name.mkStr7 args
|
||||
| .fap ``Name.mkStr8 args =>
|
||||
let mut nameAcc := Name.anonymous
|
||||
let mut processedArgs := Array.emptyWithCapacity args.size
|
||||
for arg in args do
|
||||
let (ref, str) ← compileStrArg arg
|
||||
nameAcc := .str nameAcc str
|
||||
processedArgs := processedArgs.push (ref, nameAcc.hash)
|
||||
return .nameMkStr processedArgs
|
||||
| .pap c ys => return .pap c (← compileArgs ys)
|
||||
| .fap c #[] =>
|
||||
guard <| isSimpleGroundDecl (← getEnv) c
|
||||
return .reference c
|
||||
| _ => failure
|
||||
|
||||
compileArg (arg : Arg) : M SimpleGroundArg := do
|
||||
match arg with
|
||||
| .var var =>
|
||||
let .arg arg := (← get).groundMap[var]! | failure
|
||||
return arg
|
||||
| .erased => return .tagged 0
|
||||
|
||||
compileArgs (args : Array Arg) : M (Array SimpleGroundArg) := do
|
||||
args.mapM compileArg
|
||||
|
||||
compileStrArg (arg : Arg) : M (Name × String) := do
|
||||
let .var var := arg | failure
|
||||
let (.arg (.reference ref)) := (← get).groundMap[var]! | failure
|
||||
let some (.string val) := getSimpleGroundExprWithResolvedRefs (← getEnv) ref | failure
|
||||
return (ref, val)
|
||||
|
||||
interpStringLiteral (arg : SimpleGroundArg) : M String := do
|
||||
let .reference ref := arg | failure
|
||||
let some (.string val) := getSimpleGroundExprWithResolvedRefs (← getEnv) ref | failure
|
||||
return val
|
||||
|
||||
interpNameLiteral (arg : SimpleGroundArg) : M Name := do
|
||||
match arg with
|
||||
| .tagged 0 => return .anonymous
|
||||
| .reference ref =>
|
||||
match getSimpleGroundExprWithResolvedRefs (← getEnv) ref with
|
||||
| some (.ctor 1 #[pre, .reference ref] _ _) =>
|
||||
let pre ← interpNameLiteral pre
|
||||
let str ← interpStringLiteral (.reference ref)
|
||||
return .str pre str
|
||||
| some (.ctor 2 #[pre, .tagged i] _ _) =>
|
||||
let pre ← interpNameLiteral pre
|
||||
return .num pre i
|
||||
| some (.nameMkStr args) =>
|
||||
args.foldlM (init := .anonymous) fun acc (ref, _) => do
|
||||
let part ← interpStringLiteral (.reference ref)
|
||||
return .str acc part
|
||||
| _ => failure
|
||||
| _ => failure
|
||||
|
||||
|
||||
/--
|
||||
Detect whether `d` can be compiled to a `SimpleGroundExpr`. If it can record the associated
|
||||
`SimpleGroundExpr` into the environment for later processing by code emission.
|
||||
-/
|
||||
public def Decl.detectSimpleGround (d : Decl) : CompilerM Unit := do
|
||||
let .fdecl (body := body) (xs := params) (type := type) .. := d | return ()
|
||||
if type.isPossibleRef && params.isEmpty then
|
||||
if let some groundExpr ← compileToSimpleGroundExpr body then
|
||||
trace[compiler.ir.simple_ground] m!"Marked {d.name} as simple ground expr"
|
||||
modifyEnv fun env => addSimpleGroundDecl env d.name groundExpr
|
||||
|
||||
builtin_initialize registerTraceClass `compiler.ir.simple_ground (inherited := true)
|
||||
|
||||
end IR
|
||||
|
||||
end Lean
|
||||
@@ -7,6 +7,7 @@ module
|
||||
|
||||
prelude
|
||||
public import Lean.Attributes
|
||||
import Lean.Meta.RecExt
|
||||
|
||||
public section
|
||||
|
||||
@@ -33,14 +34,8 @@ private def isValidMacroInline (declName : Name) : CoreM Bool := do
|
||||
unless info.all.length = 1 do
|
||||
-- We do not allow `[macro_inline]` attributes at mutual recursive definitions
|
||||
return false
|
||||
let env ← getEnv
|
||||
let isRec (declName' : Name) : Bool :=
|
||||
isBRecOnRecursor env declName' ||
|
||||
declName' == ``WellFounded.fix ||
|
||||
declName' == ``WellFounded.Nat.fix ||
|
||||
declName' == declName ++ `_unary -- Auxiliary declaration created by `WF` module
|
||||
if Option.isSome <| info.value.find? fun e => e.isConst && isRec e.constName! then
|
||||
-- It contains a `brecOn` or `WellFounded.fix` application. So, it should be recursvie
|
||||
if (← Meta.isRecursiveDefinition declName) then
|
||||
-- It is recursive
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
@@ -56,9 +56,9 @@ public def Environment.getModulePackageByIdx? (env : Environment) (idx : ModuleI
|
||||
Returns the standard base of the native symbol for the compiled constant {lean}`declName`.
|
||||
|
||||
For many constants, this is the full symbol. However, initializers have an additional prefix
|
||||
(i.e., {lit}`_init_`) and boxed functions have an additional suffix (i.e., {lit}`___boxed`).
|
||||
Furthermore, some constants do not use this stem at all (e.g., {lit}`main` and definitions
|
||||
with {lit}`@[export]`).
|
||||
(i.e., {lit}`_init_`) and boxed functions have an additional suffix
|
||||
(see {name}`mkMangledBoxedName`). Furthermore, some constants do not use this stem at all
|
||||
(e.g., {lit}`main` and definitions with {lit}`@[export]`).
|
||||
-/
|
||||
@[export lean_get_symbol_stem]
|
||||
public def getSymbolStem (env : Environment) (declName : Name) : String :=
|
||||
|
||||
@@ -7,7 +7,7 @@ module
|
||||
|
||||
prelude
|
||||
public import Lean.Setup
|
||||
import Init.Data.String.Termination
|
||||
import Init.Data.String.TakeDrop
|
||||
|
||||
namespace String
|
||||
|
||||
@@ -133,6 +133,18 @@ def Name.mangleAux : Name → String
|
||||
public def Name.mangle (n : Name) (pre : String := "l_") : String :=
|
||||
pre ++ Name.mangleAux n
|
||||
|
||||
/--
|
||||
Given `s = nm.mangle pre` for some `nm : Name` and `pre : String` with `nm != Name.anonymous`,
|
||||
returns `(mkBoxedName nm).mangle pre`. This is used in the interpreter to find names of boxed
|
||||
IR declarations.
|
||||
-/
|
||||
@[export lean_mk_mangled_boxed_name]
|
||||
public def mkMangledBoxedName (s : String) : String :=
|
||||
if s.endsWith "__" then
|
||||
s ++ "_00__boxed"
|
||||
else
|
||||
s ++ "___boxed"
|
||||
|
||||
/--
|
||||
The mangled name of the name used to create the module initialization function.
|
||||
|
||||
|
||||
@@ -543,12 +543,10 @@ def logSnapshotTask (task : Language.SnapshotTask Language.SnapshotTree) : CoreM
|
||||
/-- Wraps the given action for use in `EIO.asTask` etc., discarding its final monadic state. -/
|
||||
def wrapAsync {α : Type} (act : α → CoreM β) (cancelTk? : Option IO.CancelToken) :
|
||||
CoreM (α → EIO Exception β) := do
|
||||
let (childNGen, parentNGen) := (← getNGen).mkChild
|
||||
setNGen parentNGen
|
||||
let (childDeclNGen, parentDeclNGen) := (← getDeclNGen).mkChild
|
||||
setDeclNGen parentDeclNGen
|
||||
let (childNGen, parentNGen) := (← getDeclNGen).mkChild
|
||||
setDeclNGen parentNGen
|
||||
let st ← get
|
||||
let st := { st with auxDeclNGen := childDeclNGen, ngen := childNGen }
|
||||
let st := { st with auxDeclNGen := childNGen }
|
||||
let ctx ← read
|
||||
let ctx := { ctx with cancelTk? }
|
||||
let heartbeats := (← IO.getNumHeartbeats) - ctx.initHeartbeats
|
||||
|
||||
@@ -125,7 +125,7 @@ Parses and elaborates a Verso module docstring.
|
||||
def versoModDocString
|
||||
(range : DeclarationRange) (doc : TSyntax ``document) :
|
||||
TermElabM VersoModuleDocs.Snippet := do
|
||||
let level := getVersoModuleDocs (← getEnv) |>.terminalNesting |>.map (· + 1)
|
||||
let level := getMainVersoModuleDocs (← getEnv) |>.terminalNesting |>.map (· + 1)
|
||||
Doc.elabModSnippet range (doc.raw.getArgs.map (⟨·⟩)) (level.getD 0) |>.execForModule
|
||||
|
||||
|
||||
|
||||
@@ -409,11 +409,29 @@ private builtin_initialize versoModuleDocExt :
|
||||
}
|
||||
|
||||
|
||||
def getVersoModuleDocs (env : Environment) : VersoModuleDocs :=
|
||||
/--
|
||||
Returns the Verso module docs for the current main module.
|
||||
|
||||
During elaboration, this will return the modules docs that have been added thus far, rather than
|
||||
those for the entire module.
|
||||
-/
|
||||
def getMainVersoModuleDocs (env : Environment) : VersoModuleDocs :=
|
||||
versoModuleDocExt.getState env
|
||||
|
||||
@[deprecated getMainVersoModuleDocs (since := "2026-01-21")]
|
||||
def getVersoModuleDocs := @getMainVersoModuleDocs
|
||||
|
||||
|
||||
/--
|
||||
Returns all snippets of the Verso module docs from the indicated module, if they exist.
|
||||
-/
|
||||
def getVersoModuleDoc? (env : Environment) (moduleName : Name) :
|
||||
Option (Array VersoModuleDocs.Snippet) :=
|
||||
env.getModuleIdx? moduleName |>.map fun modIdx =>
|
||||
versoModuleDocExt.getModuleEntries (level := .server) env modIdx
|
||||
|
||||
def addVersoModuleDocSnippet (env : Environment) (snippet : VersoModuleDocs.Snippet) : Except String Environment :=
|
||||
let docs := getVersoModuleDocs env
|
||||
let docs := getMainVersoModuleDocs env
|
||||
if docs.canAdd snippet then
|
||||
pure <| versoModuleDocExt.addEntry env snippet
|
||||
else throw s!"Can't add - incorrect nesting {docs.terminalNesting.map (s!"(expected at most {·})") |>.getD ""})"
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Lean.Elab.Command
|
||||
|
||||
match stx[1] with
|
||||
| Syntax.atom _ val =>
|
||||
if getVersoModuleDocs (← getEnv) |>.isEmpty then
|
||||
if getMainVersoModuleDocs (← getEnv) |>.isEmpty then
|
||||
let doc := String.Pos.Raw.extract val 0 (val.rawEndPos.unoffsetBy ⟨2⟩)
|
||||
modifyEnv fun env => addMainModuleDoc env ⟨doc, range⟩
|
||||
else
|
||||
|
||||
@@ -274,12 +274,10 @@ def wrapAsync {α β : Type} (act : α → CommandElabM β) (cancelTk? : Option
|
||||
CommandElabM (α → EIO Exception β) := do
|
||||
let ctx ← read
|
||||
let ctx := { ctx with cancelTk? }
|
||||
let (childNGen, parentNGen) := (← get).ngen.mkChild
|
||||
modify fun s => { s with ngen := parentNGen }
|
||||
let (childDeclNGen, parentDeclNGen) := (← getDeclNGen).mkChild
|
||||
setDeclNGen parentDeclNGen
|
||||
let (childNGen, parentNGen) := (← getDeclNGen).mkChild
|
||||
setDeclNGen parentNGen
|
||||
let st ← get
|
||||
let st := { st with auxDeclNGen := childDeclNGen, ngen := childNGen }
|
||||
let st := { st with auxDeclNGen := childNGen }
|
||||
return (act · |>.run ctx |>.run' st)
|
||||
|
||||
open Language in
|
||||
|
||||
@@ -907,23 +907,26 @@ def lean (name : Option Ident := none) (error warning : flag false) («show» :
|
||||
(endPos := endPos) (endPos_valid := by simp only [endPos]; split <;> simp [*])
|
||||
let cctx : Command.Context := {fileName := ← getFileName, fileMap := text, snap? := none, cancelTk? := none}
|
||||
let scopes := (← get).scopes
|
||||
let mut cmdState : Command.State := { env, maxRecDepth := ← MonadRecDepth.getMaxRecDepth, scopes }
|
||||
let mut pstate : Parser.ModuleParserState := {pos := pos, recovering := false}
|
||||
let mut cmds := #[]
|
||||
repeat
|
||||
let scope := cmdState.scopes.head!
|
||||
let pmctx := { env := cmdState.env, options := scope.opts, currNamespace := scope.currNamespace, openDecls := scope.openDecls }
|
||||
let (cmd, ps', messages) := Parser.parseCommand ictx pmctx pstate cmdState.messages
|
||||
cmds := cmds.push cmd
|
||||
pstate := ps'
|
||||
cmdState := { cmdState with messages := messages }
|
||||
cmdState ← runCommand (Command.elabCommand cmd) cmd cctx cmdState
|
||||
if Parser.isTerminalCommand cmd then break
|
||||
setEnv cmdState.env
|
||||
modify fun st => { st with scopes := cmdState.scopes }
|
||||
let (cmds, cmdState, trees) ← withSaveInfoContext do
|
||||
let mut cmdState : Command.State := { env, maxRecDepth := ← MonadRecDepth.getMaxRecDepth, scopes }
|
||||
let mut pstate : Parser.ModuleParserState := {pos := pos, recovering := false}
|
||||
let mut cmds := #[]
|
||||
repeat
|
||||
let scope := cmdState.scopes.head!
|
||||
let pmctx := { env := cmdState.env, options := scope.opts, currNamespace := scope.currNamespace, openDecls := scope.openDecls }
|
||||
let (cmd, ps', messages) := Parser.parseCommand ictx pmctx pstate cmdState.messages
|
||||
cmds := cmds.push cmd
|
||||
pstate := ps'
|
||||
cmdState := { cmdState with messages := messages }
|
||||
cmdState ← runCommand (Command.elabCommand cmd) cmd cctx cmdState
|
||||
if Parser.isTerminalCommand cmd then break
|
||||
setEnv cmdState.env
|
||||
modify fun st => { st with scopes := cmdState.scopes }
|
||||
|
||||
for t in cmdState.infoState.trees do
|
||||
pushInfoTree t
|
||||
for t in cmdState.infoState.trees do
|
||||
pushInfoTree t
|
||||
let trees := (← getInfoTrees)
|
||||
pure (cmds, cmdState, trees)
|
||||
|
||||
let mut output := #[]
|
||||
for msg in cmdState.messages.toArray do
|
||||
@@ -937,14 +940,13 @@ def lean (name : Option Ident := none) (error warning : flag false) («show» :
|
||||
let hint ← flagHint m!"The `+error` flag indicates that errors are expected:" #[" +error"]
|
||||
logErrorAt msgStx m!"Unexpected error:{indentD msg.data}{hint.getD m!""}"
|
||||
if msg.severity == .warning && !warning then
|
||||
let hint ← flagHint m!"The `+error` flag indicates that warnings are expected:" #[" +warning"]
|
||||
let hint ← flagHint m!"The `+warning` flag indicates that warnings are expected:" #[" +warning"]
|
||||
logErrorAt msgStx m!"Unexpected warning:{indentD msg.data}{hint.getD m!""}"
|
||||
else
|
||||
withRef msgStx <| log msg.data (severity := .information) (isSilent := true)
|
||||
if let some x := name then
|
||||
modifyEnv (leanOutputExt.modifyState · (·.insert x.getId output))
|
||||
if «show» then
|
||||
let trees := (← getInfoTrees)
|
||||
if h : trees.size > 0 then
|
||||
let hl := Data.LeanBlock.mk (← highlightSyntax trees (mkNullNode cmds))
|
||||
return .other {name := ``Data.LeanBlock, val := .mk hl} #[.code code.getString]
|
||||
|
||||
@@ -20,10 +20,12 @@ structure LetRecDeclView where
|
||||
declName : Name
|
||||
parentName? : Option Name
|
||||
binderIds : Array Syntax
|
||||
binders : Syntax -- binder syntax for docstring elaboration
|
||||
type : Expr
|
||||
mvar : Expr -- auxiliary metavariable used to lift the 'let rec'
|
||||
valStx : Syntax
|
||||
termination : TerminationHints
|
||||
docString? : Option (TSyntax ``Parser.Command.docComment × Bool) := none
|
||||
|
||||
structure LetRecView where
|
||||
decls : Array LetRecDeclView
|
||||
@@ -32,8 +34,9 @@ structure LetRecView where
|
||||
/- group ("let " >> nonReservedSymbol "rec ") >> sepBy1 (group (optional «attributes» >> letDecl)) ", " >> "; " >> termParser -/
|
||||
private def mkLetRecDeclView (letRec : Syntax) : TermElabM LetRecView := do
|
||||
let mut decls : Array LetRecDeclView := #[]
|
||||
let isVerso := doc.verso.get (← getOptions)
|
||||
for attrDeclStx in letRec[1][0].getSepArgs do
|
||||
let docStr? := attrDeclStx[0].getOptional?.map TSyntax.mk
|
||||
let docStr? := attrDeclStx[0].getOptional?.map (TSyntax.mk ·, isVerso)
|
||||
let attrOptStx := attrDeclStx[1]
|
||||
let attrs ← if attrOptStx.isNone then pure #[] else elabDeclAttrs attrOptStx[0]
|
||||
let decl := attrDeclStx[2][0]
|
||||
@@ -45,16 +48,21 @@ private def mkLetRecDeclView (letRec : Syntax) : TermElabM LetRecView := do
|
||||
throwErrorAt declId "'let rec' expressions must be named"
|
||||
let shortDeclName := declId.getId
|
||||
let parentName? ← getDeclName?
|
||||
let declName := parentName?.getD Name.anonymous ++ shortDeclName
|
||||
let mut declName := parentName?.getD Name.anonymous ++ shortDeclName
|
||||
let env ← getEnv
|
||||
if env.header.isModule && !env.isExporting then
|
||||
declName := mkPrivateName env declName
|
||||
if decls.any fun decl => decl.declName == declName then
|
||||
withRef declId do
|
||||
throwError "`{.ofConstName declName}` has already been declared"
|
||||
let binders := decl[1]
|
||||
let binderStx := decl[1]
|
||||
checkNotAlreadyDeclared declName
|
||||
applyAttributesAt declName attrs AttributeApplicationTime.beforeElaboration
|
||||
addDocString' declName binders docStr?
|
||||
-- Docstring processing is deferred until the declaration is added to the environment.
|
||||
-- This is necessary for Verso docstrings to work correctly, as they may reference the
|
||||
-- declaration being defined.
|
||||
addDeclarationRangesFromSyntax declName decl declId
|
||||
let binders := binders.getArgs
|
||||
let binders := binderStx.getArgs
|
||||
let typeStx := expandOptType declId decl[2]
|
||||
let (type, binderIds) ← elabBindersEx binders fun xs => do
|
||||
let type ← elabType typeStx
|
||||
@@ -70,7 +78,7 @@ private def mkLetRecDeclView (letRec : Syntax) : TermElabM LetRecView := do
|
||||
let termination ← elabTerminationHints ⟨attrDeclStx[3]⟩
|
||||
decls := decls.push {
|
||||
ref := declId, attrs, shortDeclName, declName, parentName?,
|
||||
binderIds, type, mvar, valStx, termination
|
||||
binderIds, binders := binderStx, type, mvar, valStx, termination, docString? := docStr?
|
||||
}
|
||||
else
|
||||
throwUnsupportedSyntax
|
||||
@@ -111,15 +119,12 @@ private def registerLetRecsToLift (views : Array LetRecDeclView) (fvars : Array
|
||||
let toLift ← views.mapIdxM fun i view => do
|
||||
let value := values[i]!
|
||||
let termination := view.termination.rememberExtraParams view.binderIds.size value
|
||||
let env ← getEnv
|
||||
pure {
|
||||
ref := view.ref
|
||||
fvarId := fvars[i]!.fvarId!
|
||||
attrs := view.attrs
|
||||
shortDeclName := view.shortDeclName
|
||||
declName :=
|
||||
if env.isExporting || !env.header.isModule then view.declName
|
||||
else mkPrivateName env view.declName
|
||||
declName := view.declName
|
||||
parentName? := view.parentName?
|
||||
lctx
|
||||
localInstances
|
||||
@@ -127,6 +132,8 @@ private def registerLetRecsToLift (views : Array LetRecDeclView) (fvars : Array
|
||||
val := value
|
||||
mvarId := view.mvar.mvarId!
|
||||
termination
|
||||
binders := view.binders
|
||||
docString? := view.docString?
|
||||
}
|
||||
modify fun s => { s with letRecsToLift := toLift.toList ++ s.letRecsToLift }
|
||||
|
||||
|
||||
@@ -1092,8 +1092,8 @@ def pushLetRecs (preDefs : Array PreDefinition) (letRecClosures : List LetRecClo
|
||||
ref := c.ref
|
||||
declName := c.toLift.declName
|
||||
levelParams := [] -- we set it later
|
||||
binders := mkNullNode -- No docstrings, so we don't need these
|
||||
modifiers := { modifiers with attrs := c.toLift.attrs }
|
||||
binders := c.toLift.binders
|
||||
modifiers := { modifiers with attrs := c.toLift.attrs, docString? := c.toLift.docString? }
|
||||
kind, type, value,
|
||||
termination := c.toLift.termination
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ def addPreDefsFromUnary (docCtx : LocalContext × LocalInstances) (preDefs : Arr
|
||||
let preDefNonRec := unaryPreDefNonRec.filterAttrs fun attr => attr.name != `implemented_by
|
||||
let declNames := preDefs.toList.map (·.declName)
|
||||
|
||||
preDefs.forM fun preDef =>
|
||||
unless preDef.kind.isTheorem do
|
||||
markAsRecursive preDef.declName
|
||||
|
||||
-- Do not complain if the user sets @[semireducible], which usually is a noop,
|
||||
-- we recognize that below and then do not set @[irreducible]
|
||||
withOptions (allowUnsafeReducibility.set · true) do
|
||||
@@ -53,8 +57,6 @@ def cleanPreDef (preDef : PreDefinition) (cacheProofs := true) : MetaM PreDefini
|
||||
Assign final attributes to the definitions. Assumes the EqnInfos to be already present.
|
||||
-/
|
||||
def addPreDefAttributes (preDefs : Array PreDefinition) : TermElabM Unit := do
|
||||
for preDef in preDefs do
|
||||
markAsRecursive preDef.declName
|
||||
for preDef in preDefs.reverse do
|
||||
-- must happen before `generateEagerEqns`
|
||||
-- must happen in reverse order so that constants realized as part of the first decl
|
||||
|
||||
@@ -140,6 +140,8 @@ def structuralRecursion
|
||||
preDefsNonRec.forM fun preDefNonRec => do
|
||||
let preDefNonRec ← eraseRecAppSyntax preDefNonRec
|
||||
prependError m!"structural recursion failed, produced type incorrect term" do
|
||||
unless preDefNonRec.kind.isTheorem do
|
||||
markAsRecursive preDefNonRec.declName
|
||||
-- We create the `_unsafe_rec` before we abstract nested proofs.
|
||||
-- Reason: the nested proofs may be referring to the _unsafe_rec.
|
||||
addNonRec docCtx preDefNonRec (applyAttrAfterCompilation := false) (all := names.toList)
|
||||
@@ -157,7 +159,6 @@ def structuralRecursion
|
||||
-/
|
||||
registerEqnsInfo preDef (preDefs.map (·.declName)) recArgPos fixedParamPerms
|
||||
addSmartUnfoldingDef docCtx preDef recArgPos
|
||||
markAsRecursive preDef.declName
|
||||
for preDef in preDefs do
|
||||
-- must happen in separate loop so realizations can see eqnInfos of all other preDefs
|
||||
enableRealizationsForConst preDef.declName
|
||||
|
||||
@@ -82,13 +82,27 @@ def elabMPureIntro : Tactic
|
||||
replaceMainGoal [mv]
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
private def extractPureProp (e : Expr) : MetaM (Option Expr) := do
|
||||
let e ← instantiateMVarsIfMVarApp e
|
||||
let some (_, e) := e.app2? ``ULift.down | return none
|
||||
let f := e.getAppFn
|
||||
unless f.isConstOf ``SPred.pure do return none
|
||||
let args := e.getAppArgs
|
||||
if args.size < 2 then return none
|
||||
let σs := args[0]!
|
||||
let n ← TypeList.length σs
|
||||
unless n = args.size - 2 do return none
|
||||
let p := args[1]!
|
||||
return p
|
||||
|
||||
partial def _root_.Lean.MVarId.applyRflAndAndIntro (mvar : MVarId) : MetaM Unit := do
|
||||
-- The target might look like `(⌜?n = nₛ ∧ ?m = b⌝ s).down`, which we reduce to
|
||||
-- `?n = nₛ ∧ ?m = b` by `whnfD`.
|
||||
-- The target might look like `(⌜nₛ = ?n ∧ ?m = b⌝ s).down`, which we reduce to
|
||||
-- `nₛ = ?n ∧ ?m = b` with `extractPureProp`.
|
||||
-- (Recall that `⌜s = 4⌝ s` is `SPred.pure (σs:=[Nat]) (s = 4) s` and `SPred.pure` is
|
||||
-- semi-reducible.)
|
||||
let ty ← whnfD (← mvar.getType)
|
||||
trace[Elab.Tactic.Do.spec] "whnf: {ty}"
|
||||
let ty ← mvar.getType >>= instantiateMVarsIfMVarApp
|
||||
let ty ← (·.getD ty) <$> extractPureProp ty
|
||||
trace[Elab.Tactic.Do.spec] "pure Prop: {ty}"
|
||||
if ty.isAppOf ``True then
|
||||
mvar.assign (mkConst ``True.intro)
|
||||
else if let some (lhs, rhs) := ty.app2? ``And then
|
||||
@@ -127,16 +141,3 @@ def MGoal.pureTrivial (goal : MGoal) : OptionT MetaM Expr := do
|
||||
return ((), m)
|
||||
return prf
|
||||
catch _ => failure
|
||||
|
||||
/-
|
||||
def MGoal.pureRfl (goal : MGoal) : OptionT MetaM Expr := do
|
||||
let mv ← mkFreshExprMVar goal.toExpr
|
||||
let ([], _) ← try runTactic mv.mvarId! (← `(tactic| apply $(mkIdent ``Std.Do.SPred.Tactic.Pure.intro); rfl)) catch _ => failure
|
||||
| failure
|
||||
return mv
|
||||
def MGoal.pureRfl (goal : MGoal) : OptionT MetaM Expr := do
|
||||
let mv ← mkFreshExprMVar goal.toExpr
|
||||
let ([], _) ← try runTactic mv.mvarId! (← `(tactic| apply $(mkIdent ``Std.Do.SPred.Tactic.Pure.intro); rfl)) catch _ => failure
|
||||
| failure
|
||||
return mv
|
||||
-/
|
||||
|
||||
@@ -209,8 +209,8 @@ def SuccessPoint.clause (p : SuccessPoint) : Expr :=
|
||||
|
||||
/-- The last syntactic element of a `FailureCond`. -/
|
||||
inductive ExceptCondsDefault where
|
||||
/-- `()`. This means we can suggest `post⟨...⟩`. -/
|
||||
| unit
|
||||
/-- `PUnit.unit`. This means we can suggest `post⟨...⟩`. -/
|
||||
| punit
|
||||
/-- `ExceptConds.false`. This means we can suggest `⇓ _ => _`. -/
|
||||
| false
|
||||
/-- `ExceptConds.true`. This means we can suggest `⇓? _ => _`. -/
|
||||
@@ -229,7 +229,7 @@ When the default is not defeq to `ExceptConds.false`, we use it as the default.
|
||||
-/
|
||||
structure FailureCondHints where
|
||||
points : Array Expr := #[]
|
||||
default : ExceptCondsDefault := .unit
|
||||
default : ExceptCondsDefault := .punit
|
||||
|
||||
/-- Look at how `inv` is used in the `vcs` and collect hints about how `inv` should be instantiated.
|
||||
In case it succeeds, there will be
|
||||
@@ -293,8 +293,8 @@ def collectInvariantHints (vcs : Array MVarId) (inv : MVarId) (xs : Expr) (letMu
|
||||
-- Just overwrite the existing entry. Computing a join here is overkill for the few cases
|
||||
-- where this is going to be used.
|
||||
failureConds := { failureConds with points := points }
|
||||
if conds.isConstOf ``Unit.unit then
|
||||
failureConds := { failureConds with default := .unit }
|
||||
if conds.isConstOf ``PUnit.unit then
|
||||
failureConds := { failureConds with default := .punit }
|
||||
else if conds.isAppOfArity ``ExceptConds.false 1 then
|
||||
failureConds := { failureConds with default := .false }
|
||||
else if conds.isAppOfArity ``ExceptConds.true 1 then
|
||||
@@ -402,8 +402,8 @@ public def suggestInvariant (vcs : Array MVarId) (inv : MVarId) : TacticM Term :
|
||||
-- 2. However, on early return we want to suggest something using `Invariant.withEarlyReturn`.
|
||||
-- 3. When there are non-`False` failure conditions, we cannot suggest `⇓ ⟨xs, letMuts⟩ => ...`.
|
||||
-- We might be able to suggest `⇓? ⟨xs, letMuts⟩ => ...` (`True` failure condition),
|
||||
-- or `post⟨...⟩` (more than 0 failure handlers, but ending in `()`), and fall back to
|
||||
-- `by exact ⟨...⟩` (not ending in `()`).
|
||||
-- or `post⟨...⟩` (more than 0 failure handlers, but ending in `PUnit.unit`), and fall back to
|
||||
-- `by exact ⟨...⟩` (not ending in `PUnit.unit`).
|
||||
-- 4. Similarly for the `onExcept` argument of `Invariant.withEarlyReturn`.
|
||||
-- Hence the spaghetti code.
|
||||
--
|
||||
@@ -429,7 +429,7 @@ public def suggestInvariant (vcs : Array MVarId) (inv : MVarId) : TacticM Term :
|
||||
-- Now the configuration mess.
|
||||
if failureConds.points.isEmpty then
|
||||
match failureConds.default with
|
||||
| .false | .unit =>
|
||||
| .false | .punit =>
|
||||
`(Invariant.withEarlyReturn (onReturn := fun r letMuts => $onReturn) (onContinue := fun xs letMuts => $onContinue))
|
||||
-- we handle the following two cases here rather than through
|
||||
-- `postCondWithMultipleConditions` below because that would insert a superfluous `by exact _`.
|
||||
@@ -469,7 +469,7 @@ where
|
||||
postCondWithMultipleConditions (handlers : Array Term) (default : ExceptCondsDefault) : MetaM Term := do
|
||||
let handlers := Syntax.TSepArray.ofElems (sep := ",") handlers
|
||||
match default with
|
||||
| .unit => `(post⟨$handlers,*⟩)
|
||||
| .punit => `(post⟨$handlers,*⟩)
|
||||
-- See the comment in `post⟨_⟩` syntax for why we emit `by exact` here.
|
||||
| .false => `(by exact ⟨$handlers,*, ExceptConds.false⟩)
|
||||
| .true => `(by exact ⟨$handlers,*, ExceptConds.true⟩)
|
||||
|
||||
@@ -8,6 +8,7 @@ module
|
||||
prelude
|
||||
import Lean.DocString
|
||||
public import Lean.Elab.Command
|
||||
public import Lean.Parser.Tactic.Doc
|
||||
|
||||
public section
|
||||
|
||||
@@ -38,30 +39,42 @@ open Lean.Parser.Command
|
||||
| _ => throwError "Malformed 'register_tactic_tag' command"
|
||||
|
||||
/--
|
||||
Gets the first string token in a parser description. For example, for a declaration like
|
||||
`syntax "squish " term " with " term : tactic`, it returns `some "squish "`, and for a declaration
|
||||
like `syntax tactic " <;;;> " tactic : tactic`, it returns `some " <;;;> "`.
|
||||
|
||||
Returns `none` for syntax declarations that don't contain a string constant.
|
||||
Computes a table that heuristically maps parser syntax kinds to their first tokens by inspecting the
|
||||
Pratt parsing tables for the `tactic syntax kind. If a custom name is provided for the tactic, then
|
||||
it is returned instead.
|
||||
-/
|
||||
private partial def getFirstTk (e : Expr) : MetaM (Option String) := do
|
||||
match (← Meta.whnf e).getAppFnArgs with
|
||||
| (``ParserDescr.node, #[_, _, p]) => getFirstTk p
|
||||
| (``ParserDescr.trailingNode, #[_, _, _, p]) => getFirstTk p
|
||||
| (``ParserDescr.unary, #[.app _ (.lit (.strVal "withPosition")), p]) => getFirstTk p
|
||||
| (``ParserDescr.unary, #[.app _ (.lit (.strVal "atomic")), p]) => getFirstTk p
|
||||
| (``ParserDescr.binary, #[.app _ (.lit (.strVal "andthen")), p, _]) => getFirstTk p
|
||||
| (``ParserDescr.nonReservedSymbol, #[.lit (.strVal tk), _]) => pure (some tk)
|
||||
| (``ParserDescr.symbol, #[.lit (.strVal tk)]) => pure (some tk)
|
||||
| (``Parser.withAntiquot, #[_, p]) => getFirstTk p
|
||||
| (``Parser.leadingNode, #[_, _, p]) => getFirstTk p
|
||||
| (``HAndThen.hAndThen, #[_, _, _, _, p1, p2]) =>
|
||||
if let some tk ← getFirstTk p1 then pure (some tk)
|
||||
else getFirstTk (.app p2 (.const ``Unit.unit []))
|
||||
| (``Parser.nonReservedSymbol, #[.lit (.strVal tk), _]) => pure (some tk)
|
||||
| (``Parser.symbol, #[.lit (.strVal tk)]) => pure (some tk)
|
||||
| _ => pure none
|
||||
def firstTacticTokens [Monad m] [MonadEnv m] : m (NameMap String) := do
|
||||
let env ← getEnv
|
||||
|
||||
let some tactics := (Lean.Parser.parserExtension.getState env).categories.find? `tactic
|
||||
| return {}
|
||||
|
||||
let mut firstTokens : NameMap String :=
|
||||
tacticNameExt.toEnvExtension.getState env
|
||||
|>.importedEntries
|
||||
|>.push (tacticNameExt.exportEntriesFn env (tacticNameExt.getState env) .exported)
|
||||
|>.foldl (init := {}) fun names inMods =>
|
||||
inMods.foldl (init := names) fun names (k, n) =>
|
||||
names.insert k n
|
||||
|
||||
firstTokens := addFirstTokens tactics tactics.tables.leadingTable firstTokens
|
||||
firstTokens := addFirstTokens tactics tactics.tables.trailingTable firstTokens
|
||||
|
||||
return firstTokens
|
||||
where
|
||||
addFirstTokens tactics table firsts : NameMap String := Id.run do
|
||||
let mut firsts := firsts
|
||||
for (tok, ps) in table do
|
||||
-- Skip antiquotes
|
||||
if tok == `«$» then continue
|
||||
for (p, _) in ps do
|
||||
for (k, ()) in p.info.collectKinds {} do
|
||||
if tactics.kinds.contains k then
|
||||
let tok := tok.toString (escape := false)
|
||||
-- It's important here that the already-existing mapping is preserved, because it will
|
||||
-- contain any user-provided custom name, and these shouldn't be overridden.
|
||||
firsts := firsts.alter k (·.getD tok)
|
||||
return firsts
|
||||
|
||||
/--
|
||||
Creates some `MessageData` for a parser name.
|
||||
@@ -71,18 +84,14 @@ identifiable leading token, then that token is shown. Otherwise, the underlying
|
||||
without an `@`. The name includes metadata that makes infoview hovers and the like work. This
|
||||
only works for global constants, as the local context is not included.
|
||||
-/
|
||||
private def showParserName (n : Name) : MetaM MessageData := do
|
||||
private def showParserName [Monad m] [MonadEnv m] (firsts : NameMap String) (n : Name) : m MessageData := do
|
||||
let env ← getEnv
|
||||
let params :=
|
||||
env.constants.find?' n |>.map (·.levelParams.map Level.param) |>.getD []
|
||||
let tok ←
|
||||
if let some descr := env.find? n |>.bind (·.value?) then
|
||||
if let some tk ← getFirstTk descr then
|
||||
pure <| Std.Format.text tk.trimAscii.copy
|
||||
else pure <| format n
|
||||
else pure <| format n
|
||||
|
||||
let tok := ((← customTacticName n) <|> firsts.get? n).map Std.Format.text |>.getD (format n)
|
||||
pure <| .ofFormatWithInfos {
|
||||
fmt := "'" ++ .tag 0 tok ++ "'",
|
||||
fmt := "`" ++ .tag 0 tok ++ "`",
|
||||
infos :=
|
||||
.ofList [(0, .ofTermInfo {
|
||||
lctx := .empty,
|
||||
@@ -93,7 +102,6 @@ private def showParserName (n : Name) : MetaM MessageData := do
|
||||
})] _
|
||||
}
|
||||
|
||||
|
||||
/--
|
||||
Displays all available tactic tags, with documentation.
|
||||
-/
|
||||
@@ -106,20 +114,22 @@ Displays all available tactic tags, with documentation.
|
||||
for (tac, tag) in arr do
|
||||
mapping := mapping.insert tag (mapping.getD tag {} |>.insert tac)
|
||||
|
||||
let firsts ← firstTacticTokens
|
||||
|
||||
let showDocs : Option String → MessageData
|
||||
| none => .nil
|
||||
| some d => Format.line ++ MessageData.joinSep ((d.split '\n').map (toMessageData ∘ String.Slice.copy)).toList Format.line
|
||||
|
||||
let showTactics (tag : Name) : MetaM MessageData := do
|
||||
let showTactics (tag : Name) : CommandElabM MessageData := do
|
||||
match mapping.find? tag with
|
||||
| none => pure .nil
|
||||
| some tacs =>
|
||||
if tacs.isEmpty then pure .nil
|
||||
else
|
||||
let tacs := tacs.toArray.qsort (·.toString < ·.toString) |>.toList
|
||||
pure (Format.line ++ MessageData.joinSep (← tacs.mapM showParserName) ", ")
|
||||
pure (Format.line ++ MessageData.joinSep (← tacs.mapM (showParserName firsts)) ", ")
|
||||
|
||||
let tagDescrs ← liftTermElabM <| (← allTagsWithInfo).mapM fun (name, userName, docs) => do
|
||||
let tagDescrs ← (← allTagsWithInfo).mapM fun (name, userName, docs) => do
|
||||
pure <| m!"• " ++
|
||||
MessageData.nestD (m!"`{name}`" ++
|
||||
(if name.toString != userName then m!" — \"{userName}\"" else MessageData.nil) ++
|
||||
@@ -146,13 +156,13 @@ structure TacticDoc where
|
||||
/-- Any docstring extensions that have been specified -/
|
||||
extensionDocs : Array String
|
||||
|
||||
def allTacticDocs : MetaM (Array TacticDoc) := do
|
||||
def allTacticDocs (includeUnnamed : Bool := true) : MetaM (Array TacticDoc) := do
|
||||
let env ← getEnv
|
||||
let all :=
|
||||
tacticTagExt.toEnvExtension.getState (← getEnv)
|
||||
|>.importedEntries |>.push (tacticTagExt.exportEntriesFn (← getEnv) (tacticTagExt.getState (← getEnv)) .exported)
|
||||
let allTags :=
|
||||
tacticTagExt.toEnvExtension.getState env |>.importedEntries
|
||||
|>.push (tacticTagExt.exportEntriesFn env (tacticTagExt.getState env) .exported)
|
||||
let mut tacTags : NameMap NameSet := {}
|
||||
for arr in all do
|
||||
for arr in allTags do
|
||||
for (tac, tag) in arr do
|
||||
tacTags := tacTags.insert tac (tacTags.getD tac {} |>.insert tag)
|
||||
|
||||
@@ -160,15 +170,18 @@ def allTacticDocs : MetaM (Array TacticDoc) := do
|
||||
|
||||
let some tactics := (Lean.Parser.parserExtension.getState env).categories.find? `tactic
|
||||
| return #[]
|
||||
|
||||
let firstTokens ← firstTacticTokens
|
||||
|
||||
for (tac, _) in tactics.kinds do
|
||||
-- Skip noncanonical tactics
|
||||
if let some _ := alternativeOfTactic env tac then continue
|
||||
let userName : String ←
|
||||
if let some descr := env.find? tac |>.bind (·.value?) then
|
||||
if let some tk ← getFirstTk descr then
|
||||
pure tk.trimAscii.copy
|
||||
else pure tac.toString
|
||||
else pure tac.toString
|
||||
|
||||
let userName? : Option String := firstTokens.get? tac
|
||||
let userName ←
|
||||
if let some n := userName? then pure n
|
||||
else if includeUnnamed then pure tac.toString
|
||||
else continue
|
||||
|
||||
docs := docs.push {
|
||||
internalName := tac,
|
||||
|
||||
@@ -16,6 +16,7 @@ open Meta
|
||||
|
||||
structure Context extends Tactic.Context where
|
||||
ctx : Meta.Grind.Context
|
||||
sctx : Meta.Sym.Context
|
||||
methods : Grind.Methods
|
||||
params : Grind.Params
|
||||
|
||||
@@ -289,7 +290,7 @@ open Grind
|
||||
def liftGrindM (k : GrindM α) : GrindTacticM α := do
|
||||
let ctx ← read
|
||||
let s ← get
|
||||
let ((a, grindState), symState) ← liftMetaM <| StateRefT'.run ((Grind.withGTransparency k) ctx.methods.toMethodsRef ctx.ctx |>.run s.grindState) s.symState
|
||||
let ((a, grindState), symState) ← liftMetaM <| StateRefT'.run (((Grind.withGTransparency k) ctx.methods.toMethodsRef ctx.ctx |>.run s.grindState) ctx.sctx) s.symState
|
||||
modify fun s => { s with grindState, symState }
|
||||
return a
|
||||
|
||||
@@ -358,12 +359,13 @@ def mkEvalTactic' (elaborator : Name) (params : Params) : TermElabM (Goal → TS
|
||||
let eval (goal : Goal) (stx : TSyntax `grind) : GrindM (List Goal) := do
|
||||
let methods ← getMethods
|
||||
let grindCtx ← readThe Meta.Grind.Context
|
||||
let symCtx ← readThe Meta.Sym.Context
|
||||
let grindState ← get
|
||||
let symState ← getThe Sym.State
|
||||
-- **Note**: we discard changes to `Term.State`
|
||||
let (subgoals, grindState', symState') ← Term.TermElabM.run' (ctx := termCtx) (s := termState) do
|
||||
let (_, s) ← GrindTacticM.run
|
||||
(ctx := { recover := false, methods, ctx := grindCtx, params, elaborator })
|
||||
(ctx := { recover := false, methods, ctx := grindCtx, sctx := symCtx, params, elaborator })
|
||||
(s := { grindState, symState, goals := [goal] }) do
|
||||
evalGrindTactic stx.raw
|
||||
pruneSolvedGoals
|
||||
@@ -383,7 +385,7 @@ def GrindTacticM.runAtGoal (mvarId : MVarId) (params : Params) (k : GrindTacticM
|
||||
Reconsider the option `useSorry`.
|
||||
-/
|
||||
let params' := { params with config.useSorry := false }
|
||||
let (methods, ctx, state) ← liftMetaM <| GrindM.runAtGoal mvarId params' (evalTactic? := some evalTactic) fun goal => do
|
||||
let (methods, ctx, sctx, state) ← liftMetaM <| GrindM.runAtGoal mvarId params' (evalTactic? := some evalTactic) fun goal => do
|
||||
let a : Action := Action.intros 0 >> Action.assertAll
|
||||
let goals ← match (← a.run goal) with
|
||||
| .closed _ => pure []
|
||||
@@ -392,10 +394,11 @@ def GrindTacticM.runAtGoal (mvarId : MVarId) (params : Params) (k : GrindTacticM
|
||||
let ctx ← readThe Meta.Grind.Context
|
||||
/- Restore original config -/
|
||||
let ctx := { ctx with config := params.config }
|
||||
let sctx ← readThe Meta.Sym.Context
|
||||
let grindState ← get
|
||||
let symState ← getThe Sym.State
|
||||
return (methods, ctx, { grindState, symState, goals })
|
||||
return (methods, ctx, sctx, { grindState, symState, goals })
|
||||
let tctx ← read
|
||||
k { tctx with methods, ctx, params } |>.run state
|
||||
k { tctx with methods, ctx, sctx, params } |>.run state
|
||||
|
||||
end Lean.Elab.Tactic.Grind
|
||||
|
||||
@@ -167,6 +167,11 @@ structure LetRecToLift where
|
||||
val : Expr
|
||||
mvarId : MVarId
|
||||
termination : TerminationHints
|
||||
/-- The binders syntax for the declaration, used for docstring elaboration. -/
|
||||
binders : Syntax := .missing
|
||||
/-- The docstring, if present, and whether it's Verso. Docstring processing is deferred until the
|
||||
declaration is added to the environment (needed for Verso docstrings to work). -/
|
||||
docString? : Option (TSyntax ``Lean.Parser.Command.docComment × Bool) := none
|
||||
deriving Inhabited
|
||||
|
||||
/--
|
||||
|
||||
@@ -179,6 +179,13 @@ structure EnvironmentHeader where
|
||||
`ModuleIdx` for the same module.
|
||||
-/
|
||||
modules : Array EffectiveImport := #[]
|
||||
/-- For `getModuleIdx?` -/
|
||||
private moduleName2Idx : Std.HashMap Name ModuleIdx := Id.run do
|
||||
let mut m := {}
|
||||
for _h : idx in [0:modules.size] do
|
||||
let mod := modules[idx]
|
||||
m := m.insert mod.module idx
|
||||
return m
|
||||
/--
|
||||
Subset of `modules` for which `importAll` is `true`. This is assumed to be a much smaller set so
|
||||
we precompute it instead of iterating over all of `modules` multiple times. However, note that
|
||||
@@ -267,7 +274,7 @@ structure Environment where
|
||||
-/
|
||||
private irBaseExts : Array EnvExtensionState
|
||||
/-- The header contains additional information that is set at import time. -/
|
||||
header : EnvironmentHeader := {}
|
||||
header : EnvironmentHeader := private_decl% {}
|
||||
deriving Nonempty
|
||||
|
||||
/-- Exceptions that can be raised by the kernel when type checking new declarations. -/
|
||||
@@ -1174,7 +1181,7 @@ def isSafeDefinition (env : Environment) (declName : Name) : Bool :=
|
||||
| _ => false
|
||||
|
||||
def getModuleIdx? (env : Environment) (moduleName : Name) : Option ModuleIdx :=
|
||||
env.header.modules.findIdx? (·.module == moduleName)
|
||||
env.header.moduleName2Idx[moduleName]?
|
||||
|
||||
end Environment
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ unsafe def fold {α : Type} (f : Name → α → MetaM α) (e : Expr) (acc : α)
|
||||
| .app f a =>
|
||||
let fi ← getFunInfo f (some 1)
|
||||
if fi.paramInfo[0]!.isInstImplicit then
|
||||
-- Don't visit implicit arguments.
|
||||
-- Don't visit instance implicit arguments.
|
||||
visit f acc
|
||||
else
|
||||
visit a (← visit f acc)
|
||||
|
||||
@@ -8,6 +8,7 @@ module
|
||||
prelude
|
||||
public import Lean.Meta.Match.MatcherInfo
|
||||
public import Lean.DefEqAttrib
|
||||
public import Lean.Meta.RecExt
|
||||
public import Lean.Meta.LetToHave
|
||||
import Lean.Meta.AppBuilder
|
||||
|
||||
@@ -40,26 +41,6 @@ This is implemented by
|
||||
-/
|
||||
def eqnAffectingOptions : Array (Lean.Option Bool) := #[backward.eqns.nonrecursive, backward.eqns.deepRecursiveSplit]
|
||||
|
||||
/--
|
||||
Environment extension for storing which declarations are recursive.
|
||||
This information is populated by the `PreDefinition` module, but the simplifier
|
||||
uses when unfolding declarations.
|
||||
-/
|
||||
builtin_initialize recExt : TagDeclarationExtension ←
|
||||
mkTagDeclarationExtension `recExt (asyncMode := .async .asyncEnv)
|
||||
|
||||
/--
|
||||
Marks the given declaration as recursive.
|
||||
-/
|
||||
def markAsRecursive (declName : Name) : CoreM Unit :=
|
||||
modifyEnv (recExt.tag · declName)
|
||||
|
||||
/--
|
||||
Returns `true` if `declName` was defined using well-founded recursion, or structural recursion.
|
||||
-/
|
||||
def isRecursiveDefinition (declName : Name) : CoreM Bool :=
|
||||
return recExt.isTagged (← getEnv) declName
|
||||
|
||||
def eqnThmSuffixBase := "eq"
|
||||
def eqnThmSuffixBasePrefix := eqnThmSuffixBase ++ "_"
|
||||
def eqn1ThmSuffix := eqnThmSuffixBasePrefix ++ "1"
|
||||
|
||||
@@ -139,13 +139,14 @@ private partial def andProjections (e : Expr) : MetaM (Array Expr) := do
|
||||
return acc.push e
|
||||
go e (← inferType e) #[]
|
||||
|
||||
private def mkInjectiveEqTheoremValue (ctorName : Name) (targetType : Expr) : MetaM Expr := do
|
||||
private def mkInjectiveEqTheoremValue (ctorVal : ConstructorVal) (targetType : Expr) : MetaM Expr := do
|
||||
forallTelescopeReducing targetType fun xs type => do
|
||||
let mvar ← mkFreshExprSyntheticOpaqueMVar type
|
||||
let [mvarId₁, mvarId₂] ← mvar.mvarId!.apply (mkConst ``Eq.propIntro)
|
||||
| throwError "unexpected number of subgoals when proving injective theorem for constructor `{ctorName}`"
|
||||
let (h, mvarId₁) ← mvarId₁.intro1
|
||||
solveEqOfCtorEq ctorName mvarId₁ h
|
||||
| throwError "unexpected number of subgoals when proving injective theorem for constructor `{ctorVal.name}`"
|
||||
let injPrf := mkConst (mkInjectiveTheoremNameFor ctorVal.name) (ctorVal.levelParams.map mkLevelParam)
|
||||
let injPrf := mkAppN injPrf xs
|
||||
mvarId₁.assign injPrf
|
||||
let mut mvarId₂ := mvarId₂
|
||||
while true do
|
||||
let t ← mvarId₂.getType
|
||||
@@ -158,7 +159,7 @@ private def mkInjectiveEqTheoremValue (ctorName : Name) (targetType : Expr) : Me
|
||||
| _ => pure ()
|
||||
let (h, mvarId₂') ← mvarId₂.intro1
|
||||
(_, mvarId₂) ← substEq mvarId₂' h
|
||||
try mvarId₂.refl catch _ => throwError (injTheoremFailureHeader ctorName)
|
||||
try mvarId₂.refl catch _ => throwError (injTheoremFailureHeader ctorVal.name)
|
||||
mkLambdaFVars xs mvar
|
||||
|
||||
private def mkInjectiveEqTheorem (ctorVal : ConstructorVal) : MetaM Unit := do
|
||||
@@ -167,7 +168,7 @@ private def mkInjectiveEqTheorem (ctorVal : ConstructorVal) : MetaM Unit := do
|
||||
let some type ← mkInjectiveEqTheoremType? ctorVal
|
||||
| return ()
|
||||
trace[Meta.injective] "type: {type}"
|
||||
let value ← mkInjectiveEqTheoremValue ctorVal.name type
|
||||
let value ← mkInjectiveEqTheoremValue ctorVal type
|
||||
addDecl <| Declaration.thmDecl {
|
||||
name
|
||||
levelParams := ctorVal.levelParams
|
||||
|
||||
@@ -292,9 +292,8 @@ def transform
|
||||
let aux1 := mkAppN (mkConst matcherApp.matcherName matcherLevels.toList) params'
|
||||
let aux1 := mkApp aux1 motive'
|
||||
let aux1 := mkAppN aux1 discrs'
|
||||
unless (← isTypeCorrect aux1) do
|
||||
prependError m!"failed to transform matcher, type error when constructing new pre-splitter motive:{indentExpr aux1}\nfailed with" do
|
||||
check aux1
|
||||
prependError m!"failed to transform matcher, type error when constructing new pre-splitter motive:{indentExpr aux1}\nfailed with" do
|
||||
check aux1
|
||||
let origAltTypes ← inferArgumentTypesN matcherApp.alts.size aux1
|
||||
|
||||
-- We replace the matcher with the splitter
|
||||
@@ -304,9 +303,8 @@ def transform
|
||||
let aux2 := mkAppN (mkConst splitter matcherLevels.toList) params'
|
||||
let aux2 := mkApp aux2 motive'
|
||||
let aux2 := mkAppN aux2 discrs'
|
||||
unless (← isTypeCorrect aux2) do
|
||||
prependError m!"failed to transform matcher, type error when constructing splitter motive:{indentExpr aux2}\nfailed with" do
|
||||
check aux2
|
||||
prependError m!"failed to transform matcher, type error when constructing splitter motive:{indentExpr aux2}\nfailed with" do
|
||||
check aux2
|
||||
let altTypes ← inferArgumentTypesN matcherApp.alts.size aux2
|
||||
|
||||
let mut alts' := #[]
|
||||
@@ -359,8 +357,7 @@ def transform
|
||||
let aux := mkAppN (mkConst matcherApp.matcherName matcherLevels.toList) params'
|
||||
let aux := mkApp aux motive'
|
||||
let aux := mkAppN aux discrs'
|
||||
unless (← isTypeCorrect aux) do
|
||||
logError m!"failed to transform matcher, type error when constructing new motive:{indentExpr aux}"
|
||||
prependError m!"failed to transform matcher, type error when constructing new motive:{indentExpr aux}" do
|
||||
check aux
|
||||
let altTypes ← inferArgumentTypesN matcherApp.alts.size aux
|
||||
|
||||
|
||||
33
src/Lean/Meta/RecExt.lean
Normal file
33
src/Lean/Meta/RecExt.lean
Normal file
@@ -0,0 +1,33 @@
|
||||
/-
|
||||
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Lean.Attributes
|
||||
|
||||
public section
|
||||
|
||||
namespace Lean.Meta
|
||||
|
||||
/--
|
||||
Environment extension for storing which declarations are recursive.
|
||||
This information is populated by the `PreDefinition` module, but the simplifier
|
||||
uses when unfolding declarations.
|
||||
-/
|
||||
builtin_initialize recExt : TagDeclarationExtension ←
|
||||
mkTagDeclarationExtension `recExt (asyncMode := .async .asyncEnv)
|
||||
|
||||
/--
|
||||
Marks the given declaration as recursive.
|
||||
-/
|
||||
def markAsRecursive (declName : Name) : CoreM Unit :=
|
||||
modifyEnv (recExt.tag · declName)
|
||||
|
||||
/--
|
||||
Returns `true` if `declName` was defined using well-founded recursion, or structural recursion.
|
||||
-/
|
||||
def isRecursiveDefinition (declName : Name) : CoreM Bool :=
|
||||
return recExt.isTagged (← getEnv) declName
|
||||
@@ -23,13 +23,15 @@ public import Lean.Meta.Sym.Apply
|
||||
public import Lean.Meta.Sym.InferType
|
||||
public import Lean.Meta.Sym.Simp
|
||||
public import Lean.Meta.Sym.Util
|
||||
public import Lean.Meta.Sym.Eta
|
||||
public import Lean.Meta.Sym.Grind
|
||||
|
||||
/-!
|
||||
# Symbolic simulation support.
|
||||
# Symbolic computation support.
|
||||
|
||||
This module provides `SymM`, a monad for implementing symbolic simulators (e.g., verification condition generators)
|
||||
using Lean. The monad addresses performance issues found in symbolic simulators built on top of user-facing
|
||||
tactics (e.g., `apply` and `intros`).
|
||||
This module provides `SymM`, a monad for implementing symbolic computation (e.g., decision procedures and
|
||||
verification condition generators) using Lean. The monad addresses performance issues found in symbolic
|
||||
computation engines built on top of user-facing tactics (e.g., `apply` and `intros`).
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -65,14 +67,14 @@ whether `maxFVar[e]` is in `?m.lctx` — a single hash lookup, O(1).
|
||||
|
||||
**The problem:** The `isDefEq` predicate in `MetaM` is designed for elaboration and user-facing tactics.
|
||||
It supports reduction, type-class resolution, and many other features that can be expensive or have
|
||||
unpredictable running time. For symbolic simulation, where pattern matching is called frequently on
|
||||
unpredictable running time. For symbolic computation, where pattern matching is called frequently on
|
||||
large ground terms, these features become performance bottlenecks.
|
||||
|
||||
**The solution:** In `SymM`, pattern matching and definitional equality are restricted to a more syntactic,
|
||||
predictable subset. Key design choices:
|
||||
|
||||
1. **Reducible declarations are abbreviations.** Reducible declarations are eagerly expanded when indexing
|
||||
terms and when entering symbolic simulation mode. During matching, we assume abbreviations have already
|
||||
terms and when entering symbolic computation mode. During matching, we assume abbreviations have already
|
||||
been expanded.
|
||||
|
||||
**Why `MetaM` `simp` cannot make this assumption**: The simplifier in `MetaM` is designed for interactive use,
|
||||
@@ -99,7 +101,7 @@ predictable subset. Key design choices:
|
||||
4. **Types must be indexed.** Unlike proofs and instances, types cannot be ignored, without indexing them,
|
||||
pattern matching produces too many candidates. Like other abbreviations, type abbreviations are expanded.
|
||||
Note that given `def Foo : Type := Bla`, the terms `Foo` and `Bla` are *not* considered structurally
|
||||
equal in the symbolic simulator framework.
|
||||
equal in the symbolic computation framework.
|
||||
|
||||
### Skipping type checks on assignment
|
||||
|
||||
@@ -117,7 +119,7 @@ so the check is almost always skipped.
|
||||
|
||||
### `GrindM` state
|
||||
|
||||
**The problem:** In symbolic simulation, we often want to discharge many goals using proof automation such
|
||||
**The problem:** In symbolic computation, we often want to discharge many goals using proof automation such
|
||||
as `grind`. Many of these goals share very similar local contexts. If we invoke `grind` on each goal
|
||||
independently, we repeatedly reprocess the same hypotheses.
|
||||
|
||||
|
||||
@@ -44,8 +44,11 @@ first because solving it often solves `?w`.
|
||||
def mkResultPos (pattern : Pattern) : List Nat := Id.run do
|
||||
let auxPrefix := `_sym_pre
|
||||
-- Initialize "found" mask with arguments that can be synthesized by type class resolution.
|
||||
let mut found := pattern.isInstance
|
||||
let numArgs := pattern.varTypes.size
|
||||
let mut found := if let some varInfos := pattern.varInfos? then
|
||||
varInfos.argsInfo.map fun info : ProofInstArgInfo => info.isInstance
|
||||
else
|
||||
Array.replicate numArgs false
|
||||
let auxVars := pattern.varTypes.mapIdx fun i _ => mkFVar ⟨.num auxPrefix i⟩
|
||||
-- Collect arguments that occur in the pattern
|
||||
for fvarId in collectFVars {} (pattern.pattern.instantiateRev auxVars) |>.fvarIds do
|
||||
@@ -96,6 +99,10 @@ def mkValue (expr : Expr) (pattern : Pattern) (result : MatchUnifyResult) : Expr
|
||||
else
|
||||
mkAppN (expr.instantiateLevelParams pattern.levelParams result.us) result.args
|
||||
|
||||
public inductive ApplyResult where
|
||||
| failed
|
||||
| goals (mvarIds : List MVarId)
|
||||
|
||||
/--
|
||||
Applies a backward rule to a goal, returning new subgoals.
|
||||
|
||||
@@ -103,27 +110,23 @@ Applies a backward rule to a goal, returning new subgoals.
|
||||
2. Assigns the goal metavariable to the theorem application
|
||||
3. Returns new goals for unassigned arguments (per `resultPos`)
|
||||
|
||||
Returns `none` if unification fails.
|
||||
Returns `.notApplicable` if unification fails.
|
||||
-/
|
||||
public def BackwardRule.apply? (mvarId : MVarId) (rule : BackwardRule) : SymM (Option (List MVarId)) := mvarId.withContext do
|
||||
public def BackwardRule.apply (mvarId : MVarId) (rule : BackwardRule) : SymM ApplyResult := mvarId.withContext do
|
||||
let decl ← mvarId.getDecl
|
||||
if let some result ← rule.pattern.unify? decl.type then
|
||||
mvarId.assign (mkValue rule.expr rule.pattern result)
|
||||
return some <| rule.resultPos.map fun i =>
|
||||
return .goals <| rule.resultPos.map fun i =>
|
||||
result.args[i]!.mvarId!
|
||||
else
|
||||
return none
|
||||
return .failed
|
||||
|
||||
/--
|
||||
Similar to `BackwardRule.apply?`, but throws an error if unification fails.
|
||||
Similar to `BackwardRule.apply', but throws an error if unification fails.
|
||||
-/
|
||||
public def BackwardRule.apply (mvarId : MVarId) (rule : BackwardRule) : SymM (List MVarId) := mvarId.withContext do
|
||||
let decl ← mvarId.getDecl
|
||||
if let some result ← rule.pattern.unify? decl.type then
|
||||
mvarId.assign (mkValue rule.expr rule.pattern result)
|
||||
return rule.resultPos.map fun i =>
|
||||
result.args[i]!.mvarId!
|
||||
else
|
||||
throwError "rule is not applicable to goal{mvarId}rule:{indentExpr rule.expr}"
|
||||
public def BackwardRule.apply' (mvarId : MVarId) (rule : BackwardRule) : SymM (List MVarId) := do
|
||||
let .goals mvarIds ← rule.apply mvarId
|
||||
| throwError "rule is not applicable to goal{mvarId}rule:{indentExpr rule.expr}"
|
||||
return mvarIds
|
||||
|
||||
end Lean.Meta.Sym
|
||||
|
||||
53
src/Lean/Meta/Sym/Eta.lean
Normal file
53
src/Lean/Meta/Sym/Eta.lean
Normal file
@@ -0,0 +1,53 @@
|
||||
/-
|
||||
Copyright (c) 2026 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
|
||||
-/
|
||||
module
|
||||
prelude
|
||||
public import Lean.Meta.Sym.ExprPtr
|
||||
public import Lean.Meta.Basic
|
||||
import Lean.Meta.Transform
|
||||
namespace Lean.Meta.Sym
|
||||
/--
|
||||
Checks if `body` is eta-expanded with `n` applications: `f (.bvar (n-1)) ... (.bvar 0)`.
|
||||
Returns `f` if so and `f` has no loose bvars; otherwise returns `default`.
|
||||
- `n`: number of remaining applications to check
|
||||
- `i`: expected bvar index (starts at 0, increments with each application)
|
||||
- `default`: returned when not eta-reducible (enables pointer equality check)
|
||||
-/
|
||||
def etaReduceAux (body : Expr) (n : Nat) (i : Nat) (default : Expr) : Expr := Id.run do
|
||||
match n with
|
||||
| 0 => if body.hasLooseBVars then default else body
|
||||
| n+1 =>
|
||||
let .app f (.bvar j) := body | default
|
||||
if j == i then etaReduceAux f n (i+1) default else default
|
||||
|
||||
/--
|
||||
If `e` is of the form `(fun x₁ ... xₙ => f x₁ ... xₙ)` and `f` does not contain `x₁`, ..., `xₙ`,
|
||||
then returns `f`. Otherwise, returns `e`.
|
||||
|
||||
Returns the original expression when not reducible to enable pointer equality checks.
|
||||
-/
|
||||
public def etaReduce (e : Expr) : Expr :=
|
||||
go e 0
|
||||
where
|
||||
go (body : Expr) (n : Nat) : Expr :=
|
||||
match body with
|
||||
| .lam _ _ b _ => go b (n+1)
|
||||
| _ => if n == 0 then e else etaReduceAux body n 0 e
|
||||
|
||||
/-- Returns `true` if `e` can be eta-reduced. Uses pointer equality for efficiency. -/
|
||||
public def isEtaReducible (e : Expr) : Bool :=
|
||||
!isSameExpr e (etaReduce e)
|
||||
|
||||
/-- Applies `etaReduce` to all subexpressions. Returns `e` unchanged if no subexpression is eta-reducible. -/
|
||||
public def etaReduceAll (e : Expr) : MetaM Expr := do
|
||||
unless Option.isSome <| e.find? isEtaReducible do return e
|
||||
let pre (e : Expr) : MetaM TransformStep := do
|
||||
let e' := etaReduce e
|
||||
if isSameExpr e e' then return .continue
|
||||
else return .visit e'
|
||||
Meta.transform e (pre := pre)
|
||||
|
||||
end Lean.Meta.Sym
|
||||
129
src/Lean/Meta/Sym/Grind.lean
Normal file
129
src/Lean/Meta/Sym/Grind.lean
Normal file
@@ -0,0 +1,129 @@
|
||||
/-
|
||||
Copyright (c) 2026 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
|
||||
-/
|
||||
module
|
||||
prelude
|
||||
public import Lean.Meta.Tactic.Grind.Types
|
||||
public import Lean.Meta.Sym.Simp.SimpM
|
||||
public import Lean.Meta.Sym.Apply
|
||||
import Lean.Meta.Tactic.Grind.Main
|
||||
import Lean.Meta.Sym.Simp.Goal
|
||||
import Lean.Meta.Sym.Intro
|
||||
import Lean.Meta.Sym.Util
|
||||
import Lean.Meta.Tactic.Grind.Solve
|
||||
import Lean.Meta.Tactic.Assumption
|
||||
namespace Lean.Meta.Grind
|
||||
|
||||
/-!
|
||||
# Grind Goal API for Symbolic Simulation
|
||||
|
||||
This module provides an API for building symbolic simulation engines and
|
||||
verification condition generators on top of `grind`. It wraps `Sym` operations
|
||||
to work with `grind`'s `Goal` type, enabling users to carry `grind` state
|
||||
through symbolic execution while using lightweight `Sym` operations for
|
||||
the main loop.
|
||||
|
||||
## Typical usage pattern
|
||||
```
|
||||
let goal ← mkGoal mvarId
|
||||
let .goal xs goal ← goal.introN 2 | failure
|
||||
let .goal goal ← goal.simp methods | failure
|
||||
let goal ← goal.internalizeAll
|
||||
-- ... symbolic execution loop using goal.apply ...
|
||||
let .closed ← goal.grind | failure
|
||||
```
|
||||
|
||||
## Design
|
||||
|
||||
Operations like `introN`, `apply`, and `simp` run in `SymM` for performance.
|
||||
`internalize` and `grind` run in `GrindM` to access the E-graph.
|
||||
-/
|
||||
|
||||
|
||||
/--
|
||||
Creates a `Goal` from an `MVarId`, applying `Sym` preprocessing.
|
||||
Preprocessing ensures the goal is compatible with `Sym` operations.
|
||||
-/
|
||||
public def mkGoal (mvarId : MVarId) : GrindM Goal := do
|
||||
let mvarId ← Sym.preprocessMVar mvarId
|
||||
mkGoalCore mvarId
|
||||
|
||||
open Sym (SymM)
|
||||
|
||||
public inductive IntrosResult where
|
||||
| failed
|
||||
| goal (newDecls : Array FVarId) (goal : Goal)
|
||||
|
||||
/-- Introduces `num` binders from the goal's target. -/
|
||||
public def Goal.introN (goal : Goal) (num : Nat) : SymM IntrosResult := do
|
||||
let .goal xs mvarId ← Sym.introN goal.mvarId num | return .failed
|
||||
return .goal xs { goal with mvarId }
|
||||
|
||||
/-- Introduces binders with the specified names. -/
|
||||
public def Goal.intros (goal : Goal) (names : Array Name) : SymM IntrosResult := do
|
||||
let .goal xs mvarId ← Sym.intros goal.mvarId names | return .failed
|
||||
return .goal xs { goal with mvarId }
|
||||
|
||||
public inductive ApplyResult where
|
||||
| failed
|
||||
| goals (subgoals : List Goal)
|
||||
|
||||
/-- Applies a backward rule, returning subgoals on success. -/
|
||||
public def Goal.apply (goal : Goal) (rule : Sym.BackwardRule) : SymM ApplyResult := do
|
||||
let .goals mvarIds ← rule.apply goal.mvarId | return .failed
|
||||
return .goals <| mvarIds.map fun mvarId => { goal with mvarId }
|
||||
|
||||
public inductive SimpGoalResult where
|
||||
| noProgress
|
||||
| closed
|
||||
| goal (goal : Goal)
|
||||
|
||||
/-- Simplifies the goal using the given methods. -/
|
||||
public def Goal.simp (goal : Goal) (methods : Sym.Simp.Methods := {}) (config : Sym.Simp.Config := {}) : SymM SimpGoalResult := do
|
||||
match (← Sym.simpGoal goal.mvarId methods config) with
|
||||
| .goal mvarId => return .goal { goal with mvarId }
|
||||
| .noProgress => return .noProgress
|
||||
| .closed => return .closed
|
||||
|
||||
/-- Like `simp`, but returns the original goal unchanged when no progress is made. -/
|
||||
public def Goal.simpIgnoringNoProgress (goal : Goal) (methods : Sym.Simp.Methods := {}) (config : Sym.Simp.Config := {}) : SymM SimpGoalResult := do
|
||||
match (← Sym.simpGoal goal.mvarId methods config) with
|
||||
| .goal mvarId => return .goal { goal with mvarId }
|
||||
| .noProgress => return .goal goal
|
||||
| .closed => return .closed
|
||||
|
||||
/--
|
||||
Internalizes the next `num` hypotheses from the local context into the `grind` state (e.g., its E-graph).
|
||||
-/
|
||||
public def Goal.internalize (goal : Goal) (num : Nat) : GrindM Goal := do
|
||||
Grind.processHypotheses goal (some num)
|
||||
|
||||
/-- Internalizes all (un-internalized) hypotheses from the local context into the `grind` state. -/
|
||||
public def Goal.internalizeAll (goal : Goal) : GrindM Goal := do
|
||||
Grind.processHypotheses goal none
|
||||
|
||||
public inductive GrindResult where
|
||||
| failed (goal : Goal)
|
||||
| closed
|
||||
|
||||
/--
|
||||
Attempts to close the goal using `grind`.
|
||||
Returns `.closed` on success, or `.failed` with the first subgoal that failed to be closed.
|
||||
-/
|
||||
public def Goal.grind (goal : Goal) : GrindM GrindResult := do
|
||||
if let some failure ← solve goal then
|
||||
return .failed failure
|
||||
else
|
||||
return .closed
|
||||
|
||||
/--
|
||||
Closes the goal if its target matches a hypothesis.
|
||||
Returns `true` on success.
|
||||
-/
|
||||
public def Goal.assumption (goal : Goal) : MetaM Bool := do
|
||||
-- **TODO**: add indexing
|
||||
goal.mvarId.assumptionCore
|
||||
|
||||
end Lean.Meta.Grind
|
||||
@@ -96,48 +96,39 @@ def introCore (mvarId : MVarId) (max : Nat) (names : Array Name) : SymM (Array F
|
||||
|
||||
def hugeNat := 1000000
|
||||
|
||||
public inductive IntrosResult where
|
||||
| failed
|
||||
| goal (newDecls : Array FVarId) (mvarId : MVarId)
|
||||
|
||||
/--
|
||||
Introduces leading binders (universal quantifiers and let-expressions) from the goal's target type.
|
||||
|
||||
If `names` is non-empty, introduces (at most) `names.size` binders using the provided names.
|
||||
If `names` is empty, introduces all leading binders using inaccessible names.
|
||||
|
||||
Returns the introduced free variable Ids and the updated goal.
|
||||
|
||||
Throws an error if the target type does not have a leading binder.
|
||||
Returns `.goal newDecls mvarId` with new introduced free variable Ids and the updated goal.
|
||||
Returns `.failed` if no new declaration was introduced.
|
||||
-/
|
||||
public def intros (mvarId : MVarId) (names : Array Name := #[]) : SymM (Array FVarId × MVarId) := do
|
||||
public def intros (mvarId : MVarId) (names : Array Name := #[]) : SymM IntrosResult := do
|
||||
let result ← if names.isEmpty then
|
||||
introCore mvarId hugeNat #[]
|
||||
else
|
||||
introCore mvarId names.size names
|
||||
if result.1.isEmpty then
|
||||
throwError "`intros` failed, binder expected"
|
||||
return result
|
||||
|
||||
/--
|
||||
Introduces a single binder from the goal's target type with the given name.
|
||||
|
||||
Returns the introduced free variable ID and the updated goal.
|
||||
Throws an error if the target type does not have a leading binder.
|
||||
-/
|
||||
public def intro (mvarId : MVarId) (name : Name) : SymM (FVarId × MVarId) := do
|
||||
let (fvarIds, goal') ← introCore mvarId 1 #[name]
|
||||
if h : 0 < fvarIds.size then
|
||||
return (fvarIds[0], goal')
|
||||
else
|
||||
throwError "`intro` failed, binder expected"
|
||||
return .failed
|
||||
return .goal result.1 result.2
|
||||
|
||||
/--
|
||||
Introduces exactly `num` binders from the goal's target type.
|
||||
|
||||
Returns the introduced free variable IDs and the updated goal.
|
||||
Throws an error if the target type has fewer than `num` leading binders.
|
||||
Returns `.goal newDecls mvarId` if successful where `newDecls` are the introduced free variable IDs,
|
||||
`mvarId` the updated goal.
|
||||
Returns `.failed` if it was not possible to introduce `num` new local declarations.
|
||||
-/
|
||||
public def introN (mvarId : MVarId) (num : Nat) : SymM (Array FVarId × MVarId) := do
|
||||
public def introN (mvarId : MVarId) (num : Nat) : SymM IntrosResult := do
|
||||
let result ← introCore mvarId num #[]
|
||||
unless result.1.size == num do
|
||||
throwError "`introN` failed, insufficient number of binders"
|
||||
return result
|
||||
return .failed
|
||||
return .goal result.1 result.2
|
||||
|
||||
end Lean.Meta.Sym
|
||||
|
||||
@@ -73,4 +73,14 @@ def getFinValue? (e : Expr) : OptionT Id FinValue := do
|
||||
let : NeZero n := ⟨h⟩
|
||||
return { n, val := Fin.ofNat n v }
|
||||
|
||||
def getCharValue? (e : Expr) : OptionT Id Char := do
|
||||
let_expr Char.ofNat n := e | failure
|
||||
let .lit (.natVal n) := n | failure
|
||||
return Char.ofNat n
|
||||
|
||||
def getStringValue? (e : Expr) : Option String :=
|
||||
match e with
|
||||
| .lit (.strVal s) => some s
|
||||
| _ => none
|
||||
|
||||
end Lean.Meta.Sym
|
||||
|
||||
@@ -18,6 +18,7 @@ import Lean.Meta.Sym.ProofInstInfo
|
||||
import Lean.Meta.Sym.AlphaShareBuilder
|
||||
import Lean.Meta.Sym.LitValues
|
||||
import Lean.Meta.Sym.Offset
|
||||
import Lean.Meta.Sym.Eta
|
||||
namespace Lean.Meta.Sym
|
||||
open Internal
|
||||
|
||||
@@ -64,7 +65,11 @@ def mkProofInstInfoMapFor (pattern : Expr) : MetaM (AssocList Name ProofInstInfo
|
||||
public structure Pattern where
|
||||
levelParams : List Name
|
||||
varTypes : Array Expr
|
||||
isInstance : Array Bool
|
||||
/--
|
||||
If `some argsInfo`, `argsInfo` stores whether the pattern variables are instances/proofs.
|
||||
It is `none` if no pattern variables are instance/proof.
|
||||
-/
|
||||
varInfos? : Option ProofInstInfo
|
||||
pattern : Expr
|
||||
fnInfos : AssocList Name ProofInstInfo
|
||||
/--
|
||||
@@ -78,6 +83,16 @@ public structure Pattern where
|
||||
|
||||
def uvarPrefix : Name := `_uvar
|
||||
|
||||
/-- Returns `true` if the `i`th argument / pattern variable is an instance. -/
|
||||
def Pattern.isInstance (p : Pattern) (i : Nat) : Bool := Id.run do
|
||||
let some varInfos := p.varInfos? | return false
|
||||
varInfos.argsInfo[i]!.isInstance
|
||||
|
||||
/-- Returns `true` if the `i`th argument / pattern variable is a proof. -/
|
||||
def Pattern.isProof (p : Pattern) (i : Nat) : Bool := Id.run do
|
||||
let some varInfos := p.varInfos? | return false
|
||||
varInfos.argsInfo[i]!.isProof
|
||||
|
||||
def isUVar? (n : Name) : Option Nat := Id.run do
|
||||
let .num p idx := n | return none
|
||||
unless p == uvarPrefix do return none
|
||||
@@ -144,12 +159,13 @@ where
|
||||
else
|
||||
mask
|
||||
|
||||
def mkPatternCore (levelParams : List Name) (varTypes : Array Expr) (isInstance : Array Bool)
|
||||
(pattern : Expr) : MetaM Pattern := do
|
||||
def mkPatternCore (type : Expr) (levelParams : List Name) (varTypes : Array Expr) (pattern : Expr) : MetaM Pattern := do
|
||||
let fnInfos ← mkProofInstInfoMapFor pattern
|
||||
let checkTypeMask := mkCheckTypeMask pattern varTypes.size
|
||||
let checkTypeMask? := if checkTypeMask.all (· == false) then none else some checkTypeMask
|
||||
return { levelParams, varTypes, isInstance, pattern, fnInfos, checkTypeMask? }
|
||||
let varInfos? ← forallBoundedTelescope type varTypes.size fun xs _ =>
|
||||
mkProofInstArgInfo? xs
|
||||
return { levelParams, varTypes, pattern, fnInfos, varInfos?, checkTypeMask? }
|
||||
|
||||
/--
|
||||
Creates a `Pattern` from the type of a theorem.
|
||||
@@ -168,12 +184,12 @@ public def mkPatternFromDecl (declName : Name) (num? : Option Nat := none) : Met
|
||||
let (levelParams, type) ← preprocessPattern declName
|
||||
let hugeNumber := 10000000
|
||||
let num := num?.getD hugeNumber
|
||||
let rec go (i : Nat) (type : Expr) (varTypes : Array Expr) (isInstance : Array Bool) : MetaM Pattern := do
|
||||
let rec go (i : Nat) (pattern : Expr) (varTypes : Array Expr) : MetaM Pattern := do
|
||||
if i < num then
|
||||
if let .forallE _ d b _ := type then
|
||||
return (← go (i+1) b (varTypes.push d) (isInstance.push (isClass? (← getEnv) d).isSome))
|
||||
mkPatternCore levelParams varTypes isInstance type
|
||||
go 0 type #[] #[]
|
||||
if let .forallE _ d b _ := pattern then
|
||||
return (← go (i+1) b (varTypes.push d))
|
||||
mkPatternCore type levelParams varTypes pattern
|
||||
go 0 type #[]
|
||||
|
||||
/--
|
||||
Creates a `Pattern` from an equational theorem, using the left-hand side of the equation.
|
||||
@@ -188,14 +204,14 @@ Throws an error if the theorem's conclusion is not an equality.
|
||||
-/
|
||||
public def mkEqPatternFromDecl (declName : Name) : MetaM (Pattern × Expr) := do
|
||||
let (levelParams, type) ← preprocessPattern declName
|
||||
let rec go (type : Expr) (varTypes : Array Expr) (isInstance : Array Bool) : MetaM (Pattern × Expr) := do
|
||||
if let .forallE _ d b _ := type then
|
||||
return (← go b (varTypes.push d) (isInstance.push (isClass? (← getEnv) d).isSome))
|
||||
let rec go (pattern : Expr) (varTypes : Array Expr) : MetaM (Pattern × Expr) := do
|
||||
if let .forallE _ d b _ := pattern then
|
||||
return (← go b (varTypes.push d))
|
||||
else
|
||||
let_expr Eq _ lhs rhs := type | throwError "resulting type for `{.ofConstName declName}` is not an equality"
|
||||
let pattern ← mkPatternCore levelParams varTypes isInstance lhs
|
||||
let_expr Eq _ lhs rhs := pattern | throwError "resulting type for `{.ofConstName declName}` is not an equality"
|
||||
let pattern ← mkPatternCore type levelParams varTypes lhs
|
||||
return (pattern, rhs)
|
||||
go type #[] #[]
|
||||
go type #[]
|
||||
|
||||
structure UnifyM.Context where
|
||||
pattern : Pattern
|
||||
@@ -308,7 +324,11 @@ def isAssignedMVar (e : Expr) : MetaM Bool :=
|
||||
| _ => return false
|
||||
|
||||
partial def process (p : Expr) (e : Expr) : UnifyM Bool := do
|
||||
match p with
|
||||
let e' := etaReduce e
|
||||
if !isSameExpr e e' then
|
||||
-- **Note**: We eagerly eta reduce patterns
|
||||
process p e'
|
||||
else match p with
|
||||
| .bvar bidx => assignExpr bidx e
|
||||
| .mdata _ p => process p e
|
||||
| .const declName us =>
|
||||
@@ -708,7 +728,12 @@ def isDefEqApp (tFn : Expr) (t : Expr) (s : Expr) (_ : tFn = t.getAppFn) : DefEq
|
||||
@[export lean_sym_def_eq]
|
||||
def isDefEqMainImpl (t : Expr) (s : Expr) : DefEqM Bool := do
|
||||
if isSameExpr t s then return true
|
||||
match t, s with
|
||||
-- **Note**: `etaReduce` is supposed to be fast, and does not allocate memory
|
||||
let t' := etaReduce t
|
||||
let s' := etaReduce s
|
||||
if !isSameExpr t t' || !isSameExpr s s' then
|
||||
isDefEqMain t' s'
|
||||
else match t, s with
|
||||
| .lit l₁, .lit l₂ => return l₁ == l₂
|
||||
| .sort u, .sort v => isLevelDefEqS u v
|
||||
| .lam .., .lam .. => isDefEqBindingS t s
|
||||
@@ -799,7 +824,6 @@ def mkPreResult : UnifyM MkPreResultResult := do
|
||||
| none => mkFreshLevelMVar
|
||||
let pattern := (← read).pattern
|
||||
let varTypes := pattern.varTypes
|
||||
let isInstance := pattern.isInstance
|
||||
let eAssignment := (← get).eAssignment
|
||||
let tPending := (← get).tPending
|
||||
let mut args := #[]
|
||||
@@ -820,7 +844,7 @@ def mkPreResult : UnifyM MkPreResultResult := do
|
||||
let type := varTypes[i]!
|
||||
let type ← instantiateLevelParamsS type pattern.levelParams us
|
||||
let type ← instantiateRevBetaS type args
|
||||
if isInstance[i]! then
|
||||
if pattern.isInstance i then
|
||||
if let .some val ← trySynthInstance type then
|
||||
args := args.push (← shareCommon val)
|
||||
continue
|
||||
|
||||
@@ -7,16 +7,39 @@ module
|
||||
prelude
|
||||
public import Lean.Meta.Sym.SymM
|
||||
import Lean.Meta.Sym.IsClass
|
||||
import Lean.Meta.Tactic.Grind.Util
|
||||
import Lean.Meta.Sym.Util
|
||||
import Lean.Meta.Transform
|
||||
import Lean.Meta.Sym.Eta
|
||||
namespace Lean.Meta.Sym
|
||||
|
||||
/--
|
||||
Preprocesses types that used for pattern matching and unification.
|
||||
-/
|
||||
public def preprocessType (type : Expr) : MetaM Expr := do
|
||||
let type ← Grind.unfoldReducible type
|
||||
let type ← Sym.unfoldReducible type
|
||||
let type ← Core.betaReduce type
|
||||
zetaReduce type
|
||||
let type ← zetaReduce type
|
||||
etaReduceAll type
|
||||
|
||||
/--
|
||||
Analyzes whether the given free variables (aka arguments) are proofs or instances.
|
||||
Returns `none` if no arguments are proofs or instances.
|
||||
-/
|
||||
public def mkProofInstArgInfo? (xs : Array Expr) : MetaM (Option ProofInstInfo) := do
|
||||
let env ← getEnv
|
||||
let mut argsInfo := #[]
|
||||
let mut found := false
|
||||
for x in xs do
|
||||
let type ← Meta.inferType x
|
||||
let isInstance := isClass? env type |>.isSome
|
||||
let isProof ← isProp type
|
||||
if isInstance || isProof then
|
||||
found := true
|
||||
argsInfo := argsInfo.push { isInstance, isProof }
|
||||
if found then
|
||||
return some { argsInfo }
|
||||
else
|
||||
return none
|
||||
|
||||
/--
|
||||
Analyzes the type signature of `declName` and returns information about which arguments
|
||||
@@ -25,21 +48,7 @@ are proofs or instances. Returns `none` if no arguments are proofs or instances.
|
||||
public def mkProofInstInfo? (declName : Name) : MetaM (Option ProofInstInfo) := do
|
||||
let info ← getConstInfo declName
|
||||
let type ← preprocessType info.type
|
||||
forallTelescopeReducing type fun xs _ => do
|
||||
let env ← getEnv
|
||||
let mut argsInfo := #[]
|
||||
let mut found := false
|
||||
for x in xs do
|
||||
let type ← Meta.inferType x
|
||||
let isInstance := isClass? env type |>.isSome
|
||||
let isProof ← isProp type
|
||||
if isInstance || isProof then
|
||||
found := true
|
||||
argsInfo := argsInfo.push { isInstance, isProof }
|
||||
if found then
|
||||
return some { argsInfo }
|
||||
else
|
||||
return none
|
||||
forallTelescopeReducing type fun xs _ => mkProofInstArgInfo? xs
|
||||
|
||||
/--
|
||||
Returns information about the type signature of `declName`. It contains information about which arguments
|
||||
|
||||
@@ -21,3 +21,5 @@ public import Lean.Meta.Sym.Simp.Debug
|
||||
public import Lean.Meta.Sym.Simp.EvalGround
|
||||
public import Lean.Meta.Sym.Simp.Discharger
|
||||
public import Lean.Meta.Sym.Simp.ControlFlow
|
||||
public import Lean.Meta.Sym.Simp.Goal
|
||||
public import Lean.Meta.Sym.Simp.Telescope
|
||||
|
||||
@@ -224,7 +224,7 @@ position. However, the type is only meaningful (non-`default`) when `Result` is
|
||||
`.step`, since we only need types for constructing congruence proofs. This avoids
|
||||
unnecessary type inference when no rewriting occurs.
|
||||
-/
|
||||
def simpFixedPrefix (e : Expr) (prefixSize : Nat) (suffixSize : Nat) : SimpM Result := do
|
||||
public def simpFixedPrefix (e : Expr) (prefixSize : Nat) (suffixSize : Nat) : SimpM Result := do
|
||||
let numArgs := e.getAppNumArgs
|
||||
if numArgs ≤ prefixSize then
|
||||
-- Nothing to be done
|
||||
@@ -274,7 +274,7 @@ Uses `rewritable[i]` to determine whether argument `i` should be simplified.
|
||||
For rewritable arguments, calls `simp` and uses `congrFun'`, `congrArg`, and `congr`; for fixed arguments,
|
||||
uses `congrFun` to propagate changes from earlier arguments.
|
||||
-/
|
||||
def simpInterlaced (e : Expr) (rewritable : Array Bool) : SimpM Result := do
|
||||
public def simpInterlaced (e : Expr) (rewritable : Array Bool) : SimpM Result := do
|
||||
let numArgs := e.getAppNumArgs
|
||||
if h : numArgs = 0 then
|
||||
-- Nothing to be done
|
||||
|
||||
@@ -27,16 +27,16 @@ def simpIte : Simproc := fun e => do
|
||||
let_expr f@ite α c _ a b := e | return .rfl
|
||||
match (← simp c) with
|
||||
| .rfl _ =>
|
||||
if c.isTrue then
|
||||
if (← isTrueExpr c) then
|
||||
return .step a <| mkApp3 (mkConst ``ite_true f.constLevels!) α a b
|
||||
else if c.isFalse then
|
||||
else if (← isFalseExpr c) then
|
||||
return .step b <| mkApp3 (mkConst ``ite_false f.constLevels!) α a b
|
||||
else
|
||||
return .rfl (done := true)
|
||||
| .step c' h _ =>
|
||||
if c'.isTrue then
|
||||
if (← isTrueExpr c') then
|
||||
return .step a <| mkApp (e.replaceFn ``ite_cond_eq_true) h
|
||||
else if c'.isFalse then
|
||||
else if (← isFalseExpr c') then
|
||||
return .step b <| mkApp (e.replaceFn ``ite_cond_eq_false) h
|
||||
else
|
||||
let .some inst' ← trySynthInstance (mkApp (mkConst ``Decidable) c') | return .rfl
|
||||
@@ -56,20 +56,20 @@ def simpDIte : Simproc := fun e => do
|
||||
let_expr f@dite α c _ a b := e | return .rfl
|
||||
match (← simp c) with
|
||||
| .rfl _ =>
|
||||
if c.isTrue then
|
||||
if (← isTrueExpr c) then
|
||||
let a' ← share <| a.betaRev #[mkConst ``True.intro]
|
||||
return .step a' <| mkApp3 (mkConst ``dite_true f.constLevels!) α a b
|
||||
else if c.isFalse then
|
||||
else if (← isFalseExpr c) then
|
||||
let b' ← share <| b.betaRev #[mkConst ``not_false]
|
||||
return .step b' <| mkApp3 (mkConst ``dite_false f.constLevels!) α a b
|
||||
else
|
||||
return .rfl (done := true)
|
||||
| .step c' h _ =>
|
||||
if c'.isTrue then
|
||||
if (← isTrueExpr c') then
|
||||
let h' ← shareCommon <| mkOfEqTrueCore c h
|
||||
let a ← share <| a.betaRev #[h']
|
||||
return .step a <| mkApp (e.replaceFn ``dite_cond_eq_true) h
|
||||
else if c'.isFalse then
|
||||
else if (← isFalseExpr c') then
|
||||
let h' ← shareCommon <| mkOfEqFalseCore c h
|
||||
let b ← share <| b.betaRev #[h']
|
||||
return .step b <| mkApp (e.replaceFn ``dite_cond_eq_false) h
|
||||
@@ -94,16 +94,16 @@ def simpCond : Simproc := fun e => do
|
||||
let_expr f@cond α c a b := e | return .rfl
|
||||
match (← simp c) with
|
||||
| .rfl _ =>
|
||||
if c.isConstOf ``true then
|
||||
if isSameExpr c (← getBoolTrueExpr) then
|
||||
return .step a <| mkApp3 (mkConst ``cond_true f.constLevels!) α a b
|
||||
else if c.isConstOf ``false then
|
||||
else if isSameExpr c (← getBoolFalseExpr) then
|
||||
return .step b <| mkApp3 (mkConst ``cond_false f.constLevels!) α a b
|
||||
else
|
||||
return .rfl (done := true)
|
||||
| .step c' h _ =>
|
||||
if c'.isConstOf ``true then
|
||||
if isSameExpr c' (← getBoolTrueExpr) then
|
||||
return .step a <| mkApp (e.replaceFn ``Sym.cond_cond_eq_true) h
|
||||
else if c'.isConstOf ``false then
|
||||
else if isSameExpr c' (← getBoolFalseExpr) then
|
||||
return .step b <| mkApp (e.replaceFn ``Sym.cond_cond_eq_false) h
|
||||
else
|
||||
let e' := e.getBoundedAppFn 3
|
||||
|
||||
@@ -9,6 +9,7 @@ public import Lean.Meta.Sym.Simp.SimpM
|
||||
public import Lean.Meta.Sym.Simp.Discharger
|
||||
import Lean.Meta.Sym.Simp.Theorems
|
||||
import Lean.Meta.Sym.Simp.Rewrite
|
||||
import Lean.Meta.Sym.Simp.Goal
|
||||
import Lean.Meta.Sym.Util
|
||||
import Lean.Meta.Tactic.Util
|
||||
import Lean.Meta.AppBuilder
|
||||
@@ -27,24 +28,9 @@ public def mkSimprocFor (declNames : Array Name) (d : Discharger := dischargeNon
|
||||
public def mkMethods (declNames : Array Name) : MetaM Methods := do
|
||||
return { post := (← mkSimprocFor declNames) }
|
||||
|
||||
public def simpWith (k : Expr → SymM Result) (mvarId : MVarId) : MetaM (Option MVarId) := SymM.run do
|
||||
let mvarId ← preprocessMVar mvarId
|
||||
let decl ← mvarId.getDecl
|
||||
let target := decl.type
|
||||
match (← k target) with
|
||||
| .rfl _ => throwError "`Sym.simp` made no progress "
|
||||
| .step target' h _ =>
|
||||
let mvarNew ← mkFreshExprSyntheticOpaqueMVar target' decl.userName
|
||||
let h ← mkAppM ``Eq.mpr #[h, mvarNew]
|
||||
mvarId.assign h
|
||||
if target'.isTrue then
|
||||
mvarNew.mvarId!.assign (mkConst ``True.intro)
|
||||
return none
|
||||
else
|
||||
return some mvarNew.mvarId!
|
||||
|
||||
public def simpGoal (declNames : Array Name) (mvarId : MVarId) : MetaM (Option MVarId) := SymM.run do mvarId.withContext do
|
||||
public def simpGoalUsing (declNames : Array Name) (mvarId : MVarId) : MetaM (Option MVarId) := SymM.run do
|
||||
let methods ← mkMethods declNames
|
||||
simpWith (simp · methods) mvarId
|
||||
let mvarId ← preprocessMVar mvarId
|
||||
(← simpGoal mvarId methods).toOption
|
||||
|
||||
end Lean.Meta.Sym
|
||||
|
||||
@@ -9,6 +9,7 @@ public import Lean.Meta.Sym.Simp.SimpM
|
||||
import Init.Sym.Lemmas
|
||||
import Init.Data.Int.Gcd
|
||||
import Lean.Meta.Sym.LitValues
|
||||
import Lean.Meta.Sym.AlphaShareBuilder
|
||||
namespace Lean.Meta.Sym.Simp
|
||||
|
||||
/-!
|
||||
@@ -343,10 +344,10 @@ abbrev evalBinPred (toValue? : Expr → Option α) (trueThm falseThm : Expr) (op
|
||||
let some va := toValue? a | return .rfl
|
||||
let some vb := toValue? b | return .rfl
|
||||
if op va vb then
|
||||
let e ← share <| mkConst ``True
|
||||
let e ← getTrueExpr
|
||||
return .step e (mkApp3 trueThm a b eagerReflBoolTrue) (done := true)
|
||||
else
|
||||
let e ← share <| mkConst ``False
|
||||
let e ← getFalseExpr
|
||||
return .step e (mkApp3 falseThm a b eagerReflBoolFalse) (done := true)
|
||||
|
||||
def evalBitVecPred (n : Expr) (trueThm falseThm : Expr) (op : {n : Nat} → BitVec n → BitVec n → Bool) (a b : Expr) : SimpM Result := do
|
||||
@@ -354,10 +355,10 @@ def evalBitVecPred (n : Expr) (trueThm falseThm : Expr) (op : {n : Nat} → BitV
|
||||
let some vb := getBitVecValue? b | return .rfl
|
||||
if h : va.n = vb.n then
|
||||
if op va.val (h ▸ vb.val) then
|
||||
let e ← share <| mkConst ``True
|
||||
let e ← getTrueExpr
|
||||
return .step e (mkApp4 trueThm n a b eagerReflBoolTrue) (done := true)
|
||||
else
|
||||
let e ← share <| mkConst ``False
|
||||
let e ← getFalseExpr
|
||||
return .step e (mkApp4 falseThm n a b eagerReflBoolFalse) (done := true)
|
||||
else
|
||||
return .rfl
|
||||
@@ -367,10 +368,10 @@ def evalFinPred (n : Expr) (trueThm falseThm : Expr) (op : {n : Nat} → Fin n
|
||||
let some vb := getFinValue? b | return .rfl
|
||||
if h : va.n = vb.n then
|
||||
if op va.val (h ▸ vb.val) then
|
||||
let e ← share <| mkConst ``True
|
||||
let e ← getTrueExpr
|
||||
return .step e (mkApp4 trueThm n a b eagerReflBoolTrue) (done := true)
|
||||
else
|
||||
let e ← share <| mkConst ``False
|
||||
let e ← getFalseExpr
|
||||
return .step e (mkApp4 falseThm n a b eagerReflBoolFalse) (done := true)
|
||||
else
|
||||
return .rfl
|
||||
@@ -392,6 +393,8 @@ def evalLT (α : Expr) (a b : Expr) : SimpM Result :=
|
||||
| UInt64 => evalBinPred getUInt64Value? (mkConst ``UInt64.lt_eq_true) (mkConst ``UInt64.lt_eq_false) (. < .) a b
|
||||
| Fin n => evalFinPred n (mkConst ``Fin.lt_eq_true) (mkConst ``Fin.lt_eq_false) (. < .) a b
|
||||
| BitVec n => evalBitVecPred n (mkConst ``BitVec.lt_eq_true) (mkConst ``BitVec.lt_eq_false) (. < .) a b
|
||||
| String => evalBinPred getStringValue? (mkConst ``String.lt_eq_true) (mkConst ``String.lt_eq_false) (. < .) a b
|
||||
| Char => evalBinPred getCharValue? (mkConst ``Char.lt_eq_true) (mkConst ``Char.lt_eq_false) (. < .) a b
|
||||
| _ => return .rfl
|
||||
|
||||
def evalLE (α : Expr) (a b : Expr) : SimpM Result :=
|
||||
@@ -409,45 +412,13 @@ def evalLE (α : Expr) (a b : Expr) : SimpM Result :=
|
||||
| UInt64 => evalBinPred getUInt64Value? (mkConst ``UInt64.le_eq_true) (mkConst ``UInt64.le_eq_false) (. ≤ .) a b
|
||||
| Fin n => evalFinPred n (mkConst ``Fin.le_eq_true) (mkConst ``Fin.le_eq_false) (. ≤ .) a b
|
||||
| BitVec n => evalBitVecPred n (mkConst ``BitVec.le_eq_true) (mkConst ``BitVec.le_eq_false) (. ≤ .) a b
|
||||
| _ => return .rfl
|
||||
|
||||
def evalGT (α : Expr) (a b : Expr) : SimpM Result :=
|
||||
match_expr α with
|
||||
| Nat => evalBinPred getNatValue? (mkConst ``Nat.gt_eq_true) (mkConst ``Nat.gt_eq_false) (. > .) a b
|
||||
| Int => evalBinPred getIntValue? (mkConst ``Int.gt_eq_true) (mkConst ``Int.gt_eq_false) (. > .) a b
|
||||
| Rat => evalBinPred getRatValue? (mkConst ``Rat.gt_eq_true) (mkConst ``Rat.gt_eq_false) (. > .) a b
|
||||
| Int8 => evalBinPred getInt8Value? (mkConst ``Int8.gt_eq_true) (mkConst ``Int8.gt_eq_false) (. > .) a b
|
||||
| Int16 => evalBinPred getInt16Value? (mkConst ``Int16.gt_eq_true) (mkConst ``Int16.gt_eq_false) (. > .) a b
|
||||
| Int32 => evalBinPred getInt32Value? (mkConst ``Int32.gt_eq_true) (mkConst ``Int32.gt_eq_false) (. > .) a b
|
||||
| Int64 => evalBinPred getInt64Value? (mkConst ``Int64.gt_eq_true) (mkConst ``Int64.gt_eq_false) (. > .) a b
|
||||
| UInt8 => evalBinPred getUInt8Value? (mkConst ``UInt8.gt_eq_true) (mkConst ``UInt8.gt_eq_false) (. > .) a b
|
||||
| UInt16 => evalBinPred getUInt16Value? (mkConst ``UInt16.gt_eq_true) (mkConst ``UInt16.gt_eq_false) (. > .) a b
|
||||
| UInt32 => evalBinPred getUInt32Value? (mkConst ``UInt32.gt_eq_true) (mkConst ``UInt32.gt_eq_false) (. > .) a b
|
||||
| UInt64 => evalBinPred getUInt64Value? (mkConst ``UInt64.gt_eq_true) (mkConst ``UInt64.gt_eq_false) (. > .) a b
|
||||
| Fin n => evalFinPred n (mkConst ``Fin.gt_eq_true) (mkConst ``Fin.gt_eq_false) (. > .) a b
|
||||
| BitVec n => evalBitVecPred n (mkConst ``BitVec.gt_eq_true) (mkConst ``BitVec.gt_eq_false) (. > .) a b
|
||||
| _ => return .rfl
|
||||
|
||||
def evalGE (α : Expr) (a b : Expr) : SimpM Result :=
|
||||
match_expr α with
|
||||
| Nat => evalBinPred getNatValue? (mkConst ``Nat.ge_eq_true) (mkConst ``Nat.ge_eq_false) (. ≥ .) a b
|
||||
| Int => evalBinPred getIntValue? (mkConst ``Int.ge_eq_true) (mkConst ``Int.ge_eq_false) (. ≥ .) a b
|
||||
| Rat => evalBinPred getRatValue? (mkConst ``Rat.ge_eq_true) (mkConst ``Rat.ge_eq_false) (. ≥ .) a b
|
||||
| Int8 => evalBinPred getInt8Value? (mkConst ``Int8.ge_eq_true) (mkConst ``Int8.ge_eq_false) (. ≥ .) a b
|
||||
| Int16 => evalBinPred getInt16Value? (mkConst ``Int16.ge_eq_true) (mkConst ``Int16.ge_eq_false) (. ≥ .) a b
|
||||
| Int32 => evalBinPred getInt32Value? (mkConst ``Int32.ge_eq_true) (mkConst ``Int32.ge_eq_false) (. ≥ .) a b
|
||||
| Int64 => evalBinPred getInt64Value? (mkConst ``Int64.ge_eq_true) (mkConst ``Int64.ge_eq_false) (. ≥ .) a b
|
||||
| UInt8 => evalBinPred getUInt8Value? (mkConst ``UInt8.ge_eq_true) (mkConst ``UInt8.ge_eq_false) (. ≥ .) a b
|
||||
| UInt16 => evalBinPred getUInt16Value? (mkConst ``UInt16.ge_eq_true) (mkConst ``UInt16.ge_eq_false) (. ≥ .) a b
|
||||
| UInt32 => evalBinPred getUInt32Value? (mkConst ``UInt32.ge_eq_true) (mkConst ``UInt32.ge_eq_false) (. ≥ .) a b
|
||||
| UInt64 => evalBinPred getUInt64Value? (mkConst ``UInt64.ge_eq_true) (mkConst ``UInt64.ge_eq_false) (. ≥ .) a b
|
||||
| Fin n => evalFinPred n (mkConst ``Fin.ge_eq_true) (mkConst ``Fin.ge_eq_false) (. ≥ .) a b
|
||||
| BitVec n => evalBitVecPred n (mkConst ``BitVec.ge_eq_true) (mkConst ``BitVec.ge_eq_false) (. ≥ .) a b
|
||||
| String => evalBinPred getStringValue? (mkConst ``String.le_eq_true) (mkConst ``String.le_eq_false) (. ≤ .) a b
|
||||
| Char => evalBinPred getCharValue? (mkConst ``Char.le_eq_true) (mkConst ``Char.le_eq_false) (. ≤ .) a b
|
||||
| _ => return .rfl
|
||||
|
||||
def evalEq (α : Expr) (a b : Expr) : SimpM Result :=
|
||||
if isSameExpr a b then do
|
||||
let e ← share <| mkConst ``True
|
||||
let e ← getTrueExpr
|
||||
let u ← getLevel α
|
||||
return .step e (mkApp2 (mkConst ``eq_self [u]) α a) (done := true)
|
||||
else match_expr α with
|
||||
@@ -464,27 +435,8 @@ def evalEq (α : Expr) (a b : Expr) : SimpM Result :=
|
||||
| UInt64 => evalBinPred getUInt64Value? (mkConst ``UInt64.eq_eq_true) (mkConst ``UInt64.eq_eq_false) (. = .) a b
|
||||
| Fin n => evalFinPred n (mkConst ``Fin.eq_eq_true) (mkConst ``Fin.eq_eq_false) (. = .) a b
|
||||
| BitVec n => evalBitVecPred n (mkConst ``BitVec.eq_eq_true) (mkConst ``BitVec.eq_eq_false) (. = .) a b
|
||||
| _ => return .rfl
|
||||
|
||||
def evalNe (α : Expr) (a b : Expr) : SimpM Result :=
|
||||
if isSameExpr a b then do
|
||||
let e ← share <| mkConst ``False
|
||||
let u ← getLevel α
|
||||
return .step e (mkApp2 (mkConst ``ne_self [u]) α a) (done := true)
|
||||
else match_expr α with
|
||||
| Nat => evalBinPred getNatValue? (mkConst ``Nat.ne_eq_true) (mkConst ``Nat.ne_eq_false) (. ≠ .) a b
|
||||
| Int => evalBinPred getIntValue? (mkConst ``Int.ne_eq_true) (mkConst ``Int.ne_eq_false) (. ≠ .) a b
|
||||
| Rat => evalBinPred getRatValue? (mkConst ``Rat.ne_eq_true) (mkConst ``Rat.ne_eq_false) (. ≠ .) a b
|
||||
| Int8 => evalBinPred getInt8Value? (mkConst ``Int8.ne_eq_true) (mkConst ``Int8.ne_eq_false) (. ≠ .) a b
|
||||
| Int16 => evalBinPred getInt16Value? (mkConst ``Int16.ne_eq_true) (mkConst ``Int16.ne_eq_false) (. ≠ .) a b
|
||||
| Int32 => evalBinPred getInt32Value? (mkConst ``Int32.ne_eq_true) (mkConst ``Int32.ne_eq_false) (. ≠ .) a b
|
||||
| Int64 => evalBinPred getInt64Value? (mkConst ``Int64.ne_eq_true) (mkConst ``Int64.ne_eq_false) (. ≠ .) a b
|
||||
| UInt8 => evalBinPred getUInt8Value? (mkConst ``UInt8.ne_eq_true) (mkConst ``UInt8.ne_eq_false) (. ≠ .) a b
|
||||
| UInt16 => evalBinPred getUInt16Value? (mkConst ``UInt16.ne_eq_true) (mkConst ``UInt16.ne_eq_false) (. ≠ .) a b
|
||||
| UInt32 => evalBinPred getUInt32Value? (mkConst ``UInt32.ne_eq_true) (mkConst ``UInt32.ne_eq_false) (. ≠ .) a b
|
||||
| UInt64 => evalBinPred getUInt64Value? (mkConst ``UInt64.ne_eq_true) (mkConst ``UInt64.ne_eq_false) (. ≠ .) a b
|
||||
| Fin n => evalFinPred n (mkConst ``Fin.ne_eq_true) (mkConst ``Fin.ne_eq_false) (. ≠ .) a b
|
||||
| BitVec n => evalBitVecPred n (mkConst ``BitVec.ne_eq_true) (mkConst ``BitVec.ne_eq_false) (. ≠ .) a b
|
||||
| Char => evalBinPred getCharValue? (mkConst ``Char.eq_eq_true) (mkConst ``Char.eq_eq_false) (. = .) a b
|
||||
| String => evalBinPred getStringValue? (mkConst ``String.eq_eq_true) (mkConst ``String.eq_eq_false) (. = .) a b
|
||||
| _ => return .rfl
|
||||
|
||||
def evalDvd (α : Expr) (a b : Expr) : SimpM Result :=
|
||||
@@ -554,6 +506,16 @@ macro "declare_eval_bin_bool_pred" id:ident op:term : command =>
|
||||
declare_eval_bin_bool_pred evalBEq (· == ·)
|
||||
declare_eval_bin_bool_pred evalBNe (· != ·)
|
||||
|
||||
open Internal in
|
||||
def evalNot (a : Expr) : SimpM Result :=
|
||||
/-
|
||||
**Note**: We added `evalNot` because some abbreviations expanded into `Not`s.
|
||||
-/
|
||||
match_expr a with
|
||||
| True => return .step (← getFalseExpr) (mkConst ``Sym.not_true_eq) (done := true)
|
||||
| False => return .step (← getTrueExpr) (mkConst ``Sym.not_false_eq) (done := true)
|
||||
| _ => return .rfl
|
||||
|
||||
public structure EvalStepConfig where
|
||||
maxExponent := 255
|
||||
|
||||
@@ -594,14 +556,12 @@ public def evalGround (config : EvalStepConfig := {}) : Simproc := fun e =>
|
||||
| Int.fmod a b => evalBinInt Int.fmod a b
|
||||
| Int.bmod a b => evalIntBMod a b
|
||||
| LE.le α _ a b => evalLE α a b
|
||||
| GE.ge α _ a b => evalGE α a b
|
||||
| LT.lt α _ a b => evalLT α a b
|
||||
| GT.gt α _ a b => evalGT α a b
|
||||
| Dvd.dvd α _ a b => evalDvd α a b
|
||||
| Eq α a b => evalEq α a b
|
||||
| Ne α a b => evalNe α a b
|
||||
| BEq.beq α _ a b => evalBEq α a b
|
||||
| bne α _ a b => evalBNe α a b
|
||||
| Not a => evalNot a
|
||||
| _ => return .rfl
|
||||
|
||||
end Lean.Meta.Sym.Simp
|
||||
|
||||
@@ -7,6 +7,8 @@ module
|
||||
prelude
|
||||
public import Lean.Meta.Sym.Simp.SimpM
|
||||
import Lean.Meta.Sym.AlphaShareBuilder
|
||||
import Lean.Meta.Sym.InferType
|
||||
import Lean.Meta.Sym.Simp.Result
|
||||
namespace Lean.Meta.Sym.Simp
|
||||
|
||||
/--
|
||||
@@ -25,7 +27,7 @@ The proof uses the approach used in `mkFunextFor` followed by an `Eq.ndrec`.
|
||||
def mkForallCongrFor (xs : Array Expr) : MetaM Expr := do
|
||||
let prop := mkSort 0
|
||||
let type ← mkForallFVars xs prop
|
||||
let w ← getLevel type
|
||||
let w ← Meta.getLevel type
|
||||
withLocalDeclD `p type fun p =>
|
||||
withLocalDeclD `q type fun q => do
|
||||
let eq := mkApp3 (mkConst ``Eq [1]) prop (mkAppN p xs) (mkAppN q xs)
|
||||
@@ -53,6 +55,119 @@ def mkForallCongrFor (xs : Array Expr) : MetaM Expr := do
|
||||
|
||||
open Internal
|
||||
|
||||
structure ArrowInfo where
|
||||
binderName : Name
|
||||
binderInfo : BinderInfo
|
||||
u : Level
|
||||
v : Level
|
||||
|
||||
structure ToArrowResult where
|
||||
arrow : Expr
|
||||
infos : List ArrowInfo
|
||||
v : Level
|
||||
|
||||
def toArrow (e : Expr) : SymM ToArrowResult := do
|
||||
if let .forallE n α β bi := e then
|
||||
if !β.hasLooseBVars then
|
||||
let { arrow, infos, v } ← toArrow β
|
||||
let u ← getLevel α
|
||||
let arrow ← mkAppS₂ (← mkConstS ``Arrow [u, v]) α arrow
|
||||
let info := { binderName := n, binderInfo := bi, u, v }
|
||||
return { arrow, v := mkLevelIMax' u v, infos := info :: infos }
|
||||
return { arrow := e, infos := [], v := (← getLevel e) }
|
||||
|
||||
def toForall (e : Expr) (infos : List ArrowInfo) : SymM Expr := do
|
||||
let { binderName, binderInfo, .. } :: infos := infos | return e
|
||||
let_expr Arrow α β := e | return e
|
||||
mkForallS binderName binderInfo α (← toForall β infos)
|
||||
|
||||
/--
|
||||
Recursively simplifies an `Arrow` telescope, applying telescope-specific simplifications:
|
||||
|
||||
- **False hypothesis**: `False → q` simplifies to `True` (via `false_arrow`)
|
||||
- **True hypothesis**: `True → q` simplifies to `q` (via `true_arrow`)
|
||||
- **True conclusion**: `p → True` simplifies to `True` (via `arrow_true`)
|
||||
|
||||
The first two are applicable only if `q` is in `Prop` (checked via `info.v.isZero`).
|
||||
|
||||
Returns the simplified result paired with the remaining `ArrowInfo` list. When a telescope
|
||||
collapses (e.g., to `True`), the returned `infos` list is empty, signaling to `toForall`
|
||||
that no reconstruction is needed.
|
||||
-/
|
||||
partial def simpArrows (e : Expr) (infos : List ArrowInfo) (simpBody : Simproc) : SimpM (Result × List ArrowInfo) := do
|
||||
match infos with
|
||||
| [] => return ((← simpBody e), [])
|
||||
| info :: infos' =>
|
||||
let_expr f@Arrow p q := e | return ((← simpBody e), infos)
|
||||
let p_r ← simp p
|
||||
if (← isFalseExpr (p_r.getResultExpr p)) && info.v.isZero then
|
||||
match p_r with
|
||||
| .rfl _ => return (.step (← getTrueExpr) (mkApp (mkConst ``false_arrow) q), [])
|
||||
| .step _ h _ => return (.step (← getTrueExpr) (mkApp3 (mkConst ``false_arrow_congr) p q h), [])
|
||||
let (q_r, infos') ← simpArrows q infos' simpBody
|
||||
if (← isTrueExpr (q_r.getResultExpr q)) then
|
||||
match q_r with
|
||||
| .rfl _ => return (.step (← getTrueExpr) (mkApp (mkConst ``arrow_true [info.u]) p), [])
|
||||
| .step _ h _ => return (.step (← getTrueExpr) (mkApp3 (mkConst ``arrow_true_congr [info.u]) p q h), [])
|
||||
match p_r, q_r with
|
||||
| .rfl _, .rfl _ =>
|
||||
if (← isTrueExpr p) && info.v.isZero then
|
||||
return (.step q (mkApp (mkConst ``true_arrow) q), infos')
|
||||
else
|
||||
return (.rfl, infos)
|
||||
| .step p' h _, .rfl _ =>
|
||||
if (← isTrueExpr p') && info.v.isZero then
|
||||
return (.step q (mkApp3 (mkConst ``true_arrow_congr_left) p q h), infos')
|
||||
else
|
||||
let e' ← mkAppS₂ f p' q
|
||||
return (.step e' <| mkApp4 (mkConst ``arrow_congr_left f.constLevels!) p p' q h, info :: infos')
|
||||
| .rfl _, .step q' h _ =>
|
||||
if (← isTrueExpr p) && info.v.isZero then
|
||||
return (.step q' (mkApp3 (mkConst ``true_arrow_congr_right) q q' h), infos')
|
||||
else
|
||||
let e' ← mkAppS₂ f p q'
|
||||
return (.step e' <| mkApp4 (mkConst ``arrow_congr_right f.constLevels!) p q q' h, info :: infos')
|
||||
| .step p' h₁ _, .step q' h₂ _ =>
|
||||
if (← isTrueExpr p') && info.v.isZero then
|
||||
return (.step q' (mkApp5 (mkConst ``true_arrow_congr) p q q' h₁ h₂), infos')
|
||||
else
|
||||
let e' ← mkAppS₂ f p' q'
|
||||
return (.step e' <| mkApp6 (mkConst ``arrow_congr f.constLevels!) p p' q q' h₁ h₂, info :: infos')
|
||||
|
||||
/--
|
||||
Simplifies a telescope of non-dependent arrows `p₁ → p₂ → ... → pₙ → q` by:
|
||||
1. Converting to `Arrow p₁ (Arrow p₂ (... (Arrow pₙ q)))` (see `toArrow`)
|
||||
2. Simplifying each `pᵢ` and `q` (see `simpArrows`)
|
||||
3. Converting back to `→` form (see `toForall`)
|
||||
|
||||
Using `Arrow` (a definitional wrapper around `→`) avoids the quadratic proof growth that
|
||||
occurs with `Expr.forallE`. With `forallE`, each nesting level bumps de Bruijn indices in
|
||||
subterms, destroying sharing. For example, if each `pᵢ` contains a free variable `x`, the
|
||||
de Bruijn representation of `x` differs at each depth, preventing hash-consing from
|
||||
recognizing them as identical.
|
||||
|
||||
With `Arrow`, both arguments are explicit (not under binders), so subterms remain identical
|
||||
across nesting levels and can be shared, yielding linear-sized proofs.
|
||||
|
||||
**Tradeoff**: This function simplifies each `pᵢ` and `q` individually, but misses
|
||||
simplifications that depend on the arrow structure itself. For example, `q → p → p`
|
||||
won't be simplified to `True` (when `p : Prop`) because the simplifier does not have
|
||||
a chance to apply `post` methods to the intermediate arrow `p → p`.
|
||||
|
||||
Thus, this is a simproc that is meant to be used as a pre-method and marks the
|
||||
result as fully simplified to prevent `simpArrow` from being applied.
|
||||
-/
|
||||
public def simpArrowTelescope (simpBody : Simproc := simp) : Simproc := fun e => do
|
||||
unless e.isArrow do return .rfl -- not applicable
|
||||
let { arrow, infos, v } ← toArrow e
|
||||
let (.step arrow' h _, infos) ← simpArrows arrow infos simpBody | return .rfl (done := true)
|
||||
let e' ← toForall arrow' infos
|
||||
let α := mkSort v
|
||||
let v1 := v.succ
|
||||
let h := mkApp6 (mkConst ``Eq.trans [v1]) α e arrow arrow' (mkApp2 (mkConst ``Eq.refl [v1]) α arrow) h
|
||||
let h := mkApp6 (mkConst ``Eq.trans [v1]) α e arrow' e' h (mkApp2 (mkConst ``Eq.refl [v1]) α e')
|
||||
return .step e' h (done := true)
|
||||
|
||||
public def simpArrow (e : Expr) : SimpM Result := do
|
||||
let p := e.bindingDomain!
|
||||
let q := e.bindingBody!
|
||||
@@ -75,22 +190,22 @@ public def simpArrow (e : Expr) : SimpM Result := do
|
||||
let e' ← e.updateForallS! p' q'
|
||||
return .step e' <| mkApp6 (mkConst ``implies_congr [u, v]) p p' q q' h₁ h₂
|
||||
|
||||
public def simpForall (e : Expr) : SimpM Result := do
|
||||
public def simpForall' (simpArrow : Simproc) (simpBody : Simproc) (e : Expr) : SimpM Result := do
|
||||
if e.isArrow then
|
||||
simpArrow e
|
||||
else if (← isProp e) then
|
||||
let n := getForallTelescopeSize e.bindingBody! 1
|
||||
forallBoundedTelescope e n fun xs b => withoutModifyingCacheIfNotWellBehaved do
|
||||
main xs b
|
||||
main xs (← shareCommon b)
|
||||
else
|
||||
return .rfl
|
||||
where
|
||||
main (xs : Array Expr) (b : Expr) : SimpM Result := do
|
||||
match (← simp b) with
|
||||
match (← simpBody b) with
|
||||
| .rfl _ => return .rfl
|
||||
| .step b' h _ =>
|
||||
let h ← mkLambdaFVars xs h
|
||||
let e' ← shareCommonInc (← mkForallFVars xs b')
|
||||
let e' ← shareCommon (← mkForallFVars xs b')
|
||||
-- **Note**: consider caching the forall-congr theorems
|
||||
let hcongr ← mkForallCongrFor xs
|
||||
return .step e' (mkApp3 hcongr (← mkLambdaFVars xs b) (← mkLambdaFVars xs b') h)
|
||||
@@ -101,4 +216,7 @@ where
|
||||
| .forallE _ _ b _ => if b.hasLooseBVar 0 then getForallTelescopeSize b (n+1) else n
|
||||
| _ => n
|
||||
|
||||
public def simpForall : Simproc :=
|
||||
simpForall' simpArrow simp
|
||||
|
||||
end Lean.Meta.Sym.Simp
|
||||
|
||||
81
src/Lean/Meta/Sym/Simp/Goal.lean
Normal file
81
src/Lean/Meta/Sym/Simp/Goal.lean
Normal file
@@ -0,0 +1,81 @@
|
||||
/-
|
||||
Copyright (c) 2026 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
|
||||
-/
|
||||
module
|
||||
prelude
|
||||
public import Lean.Meta.Sym.Simp.SimpM
|
||||
import Lean.Meta.Tactic.Util
|
||||
import Lean.Meta.AppBuilder
|
||||
import Lean.Meta.Sym.InferType
|
||||
namespace Lean.Meta.Sym
|
||||
/-!
|
||||
# Goal simplification
|
||||
|
||||
Applies `Sym.simp` to a goal's target type, producing a simplified goal or closing it if
|
||||
the result is `True`.
|
||||
-/
|
||||
|
||||
/-- Result of simplifying a goal with `Sym.simp`. -/
|
||||
public inductive SimpGoalResult where
|
||||
/-- No simplification was possible. -/
|
||||
| noProgress
|
||||
/-- The goal was closed (simplified to `True`). -/
|
||||
| closed
|
||||
/-- The goal was simplified to a new goal. -/
|
||||
| goal (mvarId : MVarId)
|
||||
|
||||
/--
|
||||
Converts a `SimpGoalResult` to an optional goal.
|
||||
Returns `none` if closed, `some mvarId` if simplified, or throws an error if no progress.
|
||||
-/
|
||||
public def SimpGoalResult.toOption : SimpGoalResult → CoreM (Option MVarId)
|
||||
| .noProgress => throwError "`Sym.simp` made no progress "
|
||||
| .closed => return none
|
||||
| .goal mvarId => return some mvarId
|
||||
|
||||
public def SimpGoalResult.ignoreNoProgress : SimpGoalResult → MVarId → SimpGoalResult
|
||||
| .noProgress, mvarId => .goal mvarId
|
||||
| r, _ => r
|
||||
|
||||
/--
|
||||
Converts a `Simp.Result` value into `SimpGoalResult`.
|
||||
-/
|
||||
public def Simp.Result.toSimpGoalResult (result : Simp.Result) (mvarId : MVarId) : SymM SimpGoalResult := do
|
||||
let decl ← mvarId.getDecl
|
||||
match result with
|
||||
| .rfl _ => return .noProgress
|
||||
| .step target' h _ =>
|
||||
let mvarNew ← mkFreshExprSyntheticOpaqueMVar target' decl.userName
|
||||
let u ← getLevel decl.type
|
||||
let h := mkApp4 (mkConst ``Eq.mpr [u]) decl.type target' h mvarNew
|
||||
mvarId.assign h
|
||||
if target'.isTrue then
|
||||
mvarNew.mvarId!.assign (mkConst ``True.intro)
|
||||
return .closed
|
||||
else
|
||||
return .goal mvarNew.mvarId!
|
||||
|
||||
/--
|
||||
Simplifies the target of `mvarId` using `Sym.simp`.
|
||||
Returns `.closed` if the target simplifies to `True`, `.simp mvarId'` if simplified
|
||||
to a new goal, or `.noProgress` if no simplification occurred.
|
||||
|
||||
This function assumed the input goal is a valid `Sym` goal (e.g., expressions are maximally shared).
|
||||
-/
|
||||
public def simpGoal (mvarId : MVarId) (methods : Simp.Methods := {}) (config : Simp.Config := {})
|
||||
: SymM SimpGoalResult := mvarId.withContext do
|
||||
let decl ← mvarId.getDecl
|
||||
(← simp decl.type methods config).toSimpGoalResult mvarId
|
||||
|
||||
/--
|
||||
Similar to `simpGoal`, but returns `.goal mvarId` if no progress was made.
|
||||
-/
|
||||
public def simpGoalIgnoringNoProgress (mvarId : MVarId) (methods : Simp.Methods := {}) (config : Simp.Config := {})
|
||||
: SymM SimpGoalResult := do
|
||||
match (← simpGoal mvarId methods config) with
|
||||
| .noProgress => return .goal mvarId
|
||||
| r => return r
|
||||
|
||||
end Lean.Meta.Sym
|
||||
@@ -6,7 +6,7 @@ Authors: Leonardo de Moura
|
||||
module
|
||||
prelude
|
||||
public import Lean.Meta.Sym.Simp.SimpM
|
||||
import Lean.Meta.Sym.Simp.Lambda
|
||||
public import Lean.Meta.Sym.Simp.Lambda
|
||||
import Lean.Meta.Sym.AlphaShareBuilder
|
||||
import Lean.Meta.Sym.InstantiateS
|
||||
import Lean.Meta.Sym.ReplaceS
|
||||
@@ -316,7 +316,8 @@ For each application `f a`:
|
||||
- If only `a` changed: use `congrArg : a = a' → f a = f a'`
|
||||
- If neither changed: return `.rfl`
|
||||
-/
|
||||
def simpBetaApp (e : Expr) (fType : Expr) (fnUnivs argUnivs : Array Level) : SimpM Result := do
|
||||
def simpBetaApp (e : Expr) (fType : Expr) (fnUnivs argUnivs : Array Level)
|
||||
(simpBody : Simproc) : SimpM Result := do
|
||||
return (← go e 0).1
|
||||
where
|
||||
go (e : Expr) (i : Nat) : SimpM (Result × Expr) := do
|
||||
@@ -339,7 +340,7 @@ where
|
||||
let h := mkApp6 (← mkCongrPrefix ``congr fType i) f f' a a' hf ha
|
||||
pure <| .step e' h
|
||||
return (r, fType.bindingBody!)
|
||||
| .lam .. => return (← simpLambda e, fType)
|
||||
| .lam .. => return (← simpBody e, fType)
|
||||
| _ => unreachable!
|
||||
|
||||
mkCongrPrefix (declName : Name) (fType : Expr) (i : Nat) : SymM Expr := do
|
||||
@@ -375,12 +376,12 @@ e₃ = e₄ (by rfl, definitional equality from toHave)
|
||||
e₁ = e₄ (by transitivity)
|
||||
```
|
||||
-/
|
||||
def simpHaveCore (e : Expr) : SimpM SimpHaveResult := do
|
||||
def simpHaveCore (e : Expr) (simpBody : Simproc) : SimpM SimpHaveResult := do
|
||||
let e₁ := e
|
||||
let r ← toBetaApp e₁
|
||||
let e₂ := r.e
|
||||
let { fnUnivs, argUnivs } ← getUnivs r.fType
|
||||
match (← simpBetaApp e₂ r.fType fnUnivs argUnivs) with
|
||||
match (← simpBetaApp e₂ r.fType fnUnivs argUnivs simpBody) with
|
||||
| .rfl _ => return { result := .rfl, α := r.α, u := r.u }
|
||||
| .step e₃ h _ =>
|
||||
let h₁ := mkApp6 (mkConst ``Eq.trans [r.u]) r.α e₁ e₂ e₃ r.h h
|
||||
@@ -397,8 +398,8 @@ Simplify a `have`-telescope.
|
||||
This is the main entry point for `have`-telescope simplification in `Sym.simp`.
|
||||
See module documentation for the algorithm overview.
|
||||
-/
|
||||
public def simpHave (e : Expr) : SimpM Result := do
|
||||
return (← simpHaveCore e).result
|
||||
public def simpHave (e : Expr) (simpBody : Simproc) : SimpM Result := do
|
||||
return (← simpHaveCore e simpBody).result
|
||||
|
||||
/--
|
||||
Simplify a `have`-telescope and eliminate unused bindings.
|
||||
@@ -406,8 +407,8 @@ Simplify a `have`-telescope and eliminate unused bindings.
|
||||
This combines simplification with dead variable elimination in a single pass,
|
||||
avoiding quadratic behavior from multiple passes.
|
||||
-/
|
||||
public def simpHaveAndZetaUnused (e₁ : Expr) : SimpM Result := do
|
||||
let r ← simpHaveCore e₁
|
||||
public def simpHaveAndZetaUnused (e₁ : Expr) (simpBody : Simproc) : SimpM Result := do
|
||||
let r ← simpHaveCore e₁ simpBody
|
||||
match r.result with
|
||||
| .rfl _ =>
|
||||
let e₂ ← zetaUnused e₁
|
||||
@@ -425,7 +426,7 @@ public def simpHaveAndZetaUnused (e₁ : Expr) : SimpM Result := do
|
||||
(mkApp2 (mkConst ``Eq.refl [r.u]) r.α e₃)
|
||||
return .step e₃ h
|
||||
|
||||
public def simpLet (e : Expr) : SimpM Result := do
|
||||
public def simpLet' (simpBody : Simproc) (e : Expr) : SimpM Result := do
|
||||
if !e.letNondep! then
|
||||
/-
|
||||
**Note**: We don't do anything if it is a dependent `let`.
|
||||
@@ -433,6 +434,9 @@ public def simpLet (e : Expr) : SimpM Result := do
|
||||
-/
|
||||
return .rfl
|
||||
else
|
||||
simpHaveAndZetaUnused e
|
||||
simpHaveAndZetaUnused e simpBody
|
||||
|
||||
public def simpLet : Simproc :=
|
||||
simpLet' simpLambda
|
||||
|
||||
end Lean.Meta.Sym.Simp
|
||||
|
||||
@@ -46,16 +46,16 @@ def mkFunextFor (xs : Array Expr) (β : Expr) : MetaM Expr := do
|
||||
let result ← mkLambdaFVars #[f, g, h] result
|
||||
return result
|
||||
|
||||
public def simpLambda (e : Expr) : SimpM Result := do
|
||||
public def simpLambda' (simpBody : Simproc) (e : Expr) : SimpM Result := do
|
||||
lambdaTelescope e fun xs b => withoutModifyingCacheIfNotWellBehaved do
|
||||
main xs b
|
||||
main xs (← shareCommon b)
|
||||
where
|
||||
main (xs : Array Expr) (b : Expr) : SimpM Result := do
|
||||
match (← simp b) with
|
||||
match (← simpBody b) with
|
||||
| .rfl _ => return .rfl
|
||||
| .step b' h _ =>
|
||||
let h ← mkLambdaFVars xs h
|
||||
let e' ← shareCommonInc (← mkLambdaFVars xs b')
|
||||
let e' ← shareCommon (← mkLambdaFVars xs b')
|
||||
let funext ← getFunext xs b
|
||||
return .step e' (mkApp3 funext e e' h)
|
||||
|
||||
@@ -69,4 +69,7 @@ where
|
||||
modify fun s => { s with funext := s.funext.insert { expr := key } h }
|
||||
return h
|
||||
|
||||
public def simpLambda : Simproc :=
|
||||
simpLambda' simp
|
||||
|
||||
end Lean.Meta.Sym.Simp
|
||||
|
||||
@@ -22,4 +22,12 @@ public abbrev mkEqTransResult (e₁ : Expr) (e₂ : Expr) (h₁ : Expr) (r₂ :
|
||||
| .rfl done => return .step e₂ h₁ done
|
||||
| .step e₃ h₂ done => return .step e₃ (← mkEqTrans e₁ e₂ h₁ e₃ h₂) done
|
||||
|
||||
public def Result.markAsDone : Result → Result
|
||||
| .rfl _ => .rfl true
|
||||
| .step e h _ => .step e h true
|
||||
|
||||
public def Result.getResultExpr : Expr → Result → Expr
|
||||
| e, .rfl _ => e
|
||||
| _, .step e _ _ => e
|
||||
|
||||
end Lean.Meta.Sym.Simp
|
||||
|
||||
@@ -11,6 +11,7 @@ public import Lean.Meta.Sym.Simp.Theorems
|
||||
public import Lean.Meta.Sym.Simp.App
|
||||
public import Lean.Meta.Sym.Simp.Discharger
|
||||
import Lean.Meta.Sym.InstantiateS
|
||||
import Lean.Meta.Sym.InstantiateMVarsS
|
||||
import Lean.Meta.Sym.Simp.DiscrTree
|
||||
namespace Lean.Meta.Sym.Simp
|
||||
open Grind
|
||||
@@ -20,31 +21,48 @@ Creates proof term for a rewriting step.
|
||||
Handles both constant expressions (common case, avoids `instantiateLevelParams`)
|
||||
and general expressions.
|
||||
-/
|
||||
def mkValue (expr : Expr) (pattern : Pattern) (result : MatchUnifyResult) : Expr :=
|
||||
def mkValue (expr : Expr) (pattern : Pattern) (us : List Level) (args : Array Expr) : Expr :=
|
||||
if let .const declName [] := expr then
|
||||
mkAppN (mkConst declName result.us) result.args
|
||||
mkAppN (mkConst declName us) args
|
||||
else
|
||||
mkAppN (expr.instantiateLevelParams pattern.levelParams result.us) result.args
|
||||
mkAppN (expr.instantiateLevelParams pattern.levelParams us) args
|
||||
|
||||
/--
|
||||
Tries to rewrite `e` using the given theorem.
|
||||
-/
|
||||
public def Theorem.rewrite (thm : Theorem) (e : Expr) (d : Discharger := dischargeNone) : SimpM Result := do
|
||||
public def Theorem.rewrite (thm : Theorem) (e : Expr) (d : Discharger := dischargeNone) : SimpM Result :=
|
||||
/-
|
||||
**Note**: We use `withNewMCtxDepth` to ensure auxiliary metavariables used during the `match?`
|
||||
do not pollute the metavariable context.
|
||||
Thus, we must ensure that all assigned variables have be instantiate.
|
||||
-/
|
||||
withNewMCtxDepth do
|
||||
if let some result ← thm.pattern.match? e then
|
||||
-- **Note**: Potential optimization: check whether pattern covers all variables.
|
||||
for arg in result.args do
|
||||
let .mvar mvarId := arg | pure ()
|
||||
unless (← mvarId.isAssigned) do
|
||||
let decl ← mvarId.getDecl
|
||||
if let some val ← d decl.type then
|
||||
mvarId.assign val
|
||||
let mut args := result.args.toVector
|
||||
let us ← result.us.mapM instantiateLevelMVars
|
||||
for h : i in *...args.size do
|
||||
let arg := args[i]
|
||||
if let .mvar mvarId := arg then
|
||||
if (← mvarId.isAssigned) then
|
||||
let arg ← instantiateMVarsS arg
|
||||
args := args.set i arg
|
||||
else
|
||||
-- **Note**: Failed to discharge hypothesis.
|
||||
return .rfl
|
||||
let proof := mkValue thm.expr thm.pattern result
|
||||
let rhs := thm.rhs.instantiateLevelParams thm.pattern.levelParams result.us
|
||||
let rhs ← shareCommonInc rhs
|
||||
let expr ← instantiateRevBetaS rhs result.args
|
||||
let decl ← mvarId.getDecl
|
||||
if let some val ← d decl.type then
|
||||
let val ← instantiateMVarsS val
|
||||
mvarId.assign val
|
||||
args := args.set i val
|
||||
else
|
||||
-- **Note**: Failed to discharge hypothesis.
|
||||
return .rfl
|
||||
else if arg.hasMVar then
|
||||
let arg ← instantiateMVarsS arg
|
||||
args := args.set i arg
|
||||
let proof := mkValue thm.expr thm.pattern us args.toArray
|
||||
let rhs := thm.rhs.instantiateLevelParams thm.pattern.levelParams us
|
||||
let rhs ← share rhs
|
||||
let expr ← instantiateRevBetaS rhs args.toArray
|
||||
if isSameExpr e expr then
|
||||
return .rfl
|
||||
else
|
||||
|
||||
@@ -101,7 +101,7 @@ invalidating the cache and causing O(2^n) behavior on conditional trees.
|
||||
/-- Configuration options for the structural simplifier. -/
|
||||
structure Config where
|
||||
/-- Maximum number of steps that can be performed by the simplifier. -/
|
||||
maxSteps : Nat := 1000
|
||||
maxSteps : Nat := 100_000
|
||||
/--
|
||||
Maximum depth of reentrant simplifier calls through dischargers.
|
||||
Prevents infinite loops when conditional rewrite rules trigger recursive discharge attempts.
|
||||
@@ -173,16 +173,13 @@ abbrev Cache := PHashMap ExprPtr Result
|
||||
|
||||
/-- Mutable state for the simplifier. -/
|
||||
structure State where
|
||||
/-- Number of steps performed so far. -/
|
||||
numSteps := 0
|
||||
/--
|
||||
Cache of previously simplified expressions to avoid redundant work.
|
||||
**Note**: Consider moving to `SymM.State`
|
||||
-/
|
||||
cache : Cache := {}
|
||||
/-- Stack of free variables available for reuse when re-entering binders.
|
||||
Each entry is (type pointer, fvarId). -/
|
||||
binderStack : List (ExprPtr × FVarId) := []
|
||||
/-- Number of steps performed so far. -/
|
||||
numSteps := 0
|
||||
/-- Cache for generated funext theorems -/
|
||||
funext : PHashMap ExprPtr Expr := {}
|
||||
|
||||
@@ -221,8 +218,13 @@ opaque MethodsRef.toMethods (m : MethodsRef) : Methods
|
||||
def getMethods : SimpM Methods :=
|
||||
return MethodsRef.toMethods (← read)
|
||||
|
||||
/-- Runs a `SimpM` computation with the given theorems, configuration, and initial state -/
|
||||
def SimpM.run (x : SimpM α) (methods : Methods := {}) (config : Config := {}) (s : State := {}) : SymM (α × State) := do
|
||||
let initialLCtxSize := (← getLCtx).decls.size
|
||||
x methods.toMethodsRef { initialLCtxSize, config } |>.run s
|
||||
|
||||
/-- Runs a `SimpM` computation with the given theorems and configuration. -/
|
||||
def SimpM.run (x : SimpM α) (methods : Methods := {}) (config : Config := {}) : SymM α := do
|
||||
def SimpM.run' (x : SimpM α) (methods : Methods := {}) (config : Config := {}) : SymM α := do
|
||||
let initialLCtxSize := (← getLCtx).decls.size
|
||||
x methods.toMethodsRef { initialLCtxSize, config } |>.run' {}
|
||||
|
||||
@@ -243,7 +245,8 @@ abbrev post : Simproc := fun e => do
|
||||
|
||||
abbrev withoutModifyingCache (k : SimpM α) : SimpM α := do
|
||||
let cache ← getCache
|
||||
try k finally modify fun s => { s with cache }
|
||||
let funext := (← get).funext
|
||||
try k finally modify fun s => { s with cache, funext }
|
||||
|
||||
abbrev withoutModifyingCacheIfNotWellBehaved (k : SimpM α) : SimpM α := do
|
||||
if (← getMethods).wellBehavedMethods then k else withoutModifyingCache k
|
||||
@@ -251,6 +254,6 @@ abbrev withoutModifyingCacheIfNotWellBehaved (k : SimpM α) : SimpM α := do
|
||||
end Simp
|
||||
|
||||
abbrev simp (e : Expr) (methods : Simp.Methods := {}) (config : Simp.Config := {}) : SymM Simp.Result := do
|
||||
Simp.SimpM.run (Simp.simp e) methods config
|
||||
Simp.SimpM.run' (Simp.simp e) methods config
|
||||
|
||||
end Lean.Meta.Sym
|
||||
|
||||
25
src/Lean/Meta/Sym/Simp/Telescope.lean
Normal file
25
src/Lean/Meta/Sym/Simp/Telescope.lean
Normal file
@@ -0,0 +1,25 @@
|
||||
/-
|
||||
Copyright (c) 2026 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
|
||||
-/
|
||||
module
|
||||
prelude
|
||||
public import Lean.Meta.Sym.Simp.SimpM
|
||||
import Lean.Meta.Sym.Simp.Have
|
||||
import Lean.Meta.Sym.Simp.Forall
|
||||
namespace Lean.Meta.Sym.Simp
|
||||
/--
|
||||
Simplify telescope binders (`have`-expression values, and arrow hypotheses)
|
||||
but not the final body. This simproc is useful to simplify target before
|
||||
introducing.
|
||||
-/
|
||||
public partial def simpTelescope : Simproc := fun e => do
|
||||
match e with
|
||||
| .letE .. =>
|
||||
simpLet' (simpLambda' simpTelescope) e
|
||||
| .forallE .. =>
|
||||
simpForall' (simpArrow := simpArrowTelescope simpTelescope) (simpBody := simpLambda' simpTelescope) e
|
||||
| _ => return .rfl
|
||||
|
||||
end Lean.Meta.Sym.Simp
|
||||
@@ -83,7 +83,21 @@ inductive CongrInfo where
|
||||
-/
|
||||
congrTheorem (thm : CongrTheorem)
|
||||
|
||||
/-- Mutable state for the symbolic simulator framework. -/
|
||||
/-- Pre-shared expressions for commonly used terms. -/
|
||||
structure SharedExprs where
|
||||
trueExpr : Expr
|
||||
falseExpr : Expr
|
||||
natZExpr : Expr
|
||||
btrueExpr : Expr
|
||||
bfalseExpr : Expr
|
||||
ordEqExpr : Expr
|
||||
intExpr : Expr
|
||||
|
||||
/-- Readonly context for the symbolic computation framework. -/
|
||||
structure Context where
|
||||
sharedExprs : SharedExprs
|
||||
|
||||
/-- Mutable state for the symbolic computation framework. -/
|
||||
structure State where
|
||||
/-- `ShareCommon` (aka `Hash-consing`) state. -/
|
||||
share : AlphaShareCommon.State := {}
|
||||
@@ -120,11 +134,45 @@ structure State where
|
||||
congrInfo : PHashMap ExprPtr CongrInfo := {}
|
||||
debug : Bool := false
|
||||
|
||||
abbrev SymM := StateRefT State MetaM
|
||||
abbrev SymM := ReaderT Context <| StateRefT State MetaM
|
||||
|
||||
private def mkSharedExprs : AlphaShareCommonM SharedExprs := do
|
||||
let falseExpr ← shareCommonAlphaInc <| mkConst ``False
|
||||
let trueExpr ← shareCommonAlphaInc <| mkConst ``True
|
||||
let bfalseExpr ← shareCommonAlphaInc <| mkConst ``Bool.false
|
||||
let btrueExpr ← shareCommonAlphaInc <| mkConst ``Bool.true
|
||||
let natZExpr ← shareCommonAlphaInc <| mkNatLit 0
|
||||
let ordEqExpr ← shareCommonAlphaInc <| mkConst ``Ordering.eq
|
||||
let intExpr ← shareCommonAlphaInc <| Int.mkType
|
||||
return { falseExpr, trueExpr, bfalseExpr, btrueExpr, natZExpr, ordEqExpr, intExpr }
|
||||
|
||||
def SymM.run (x : SymM α) : MetaM α := do
|
||||
let (sharedExprs, share) := mkSharedExprs |>.run {}
|
||||
let debug := sym.debug.get (← getOptions)
|
||||
x |>.run' { debug }
|
||||
x { sharedExprs } |>.run' { debug, share }
|
||||
|
||||
/-- Returns maximally shared commonly used terms -/
|
||||
def getSharedExprs : SymM SharedExprs :=
|
||||
return (← read).sharedExprs
|
||||
|
||||
/-- Returns the internalized `True` constant. -/
|
||||
def getTrueExpr : SymM Expr := return (← getSharedExprs).trueExpr
|
||||
/-- Returns `true` if `e` is the internalized `True` expression. -/
|
||||
def isTrueExpr (e : Expr) : SymM Bool := return isSameExpr e (← getTrueExpr)
|
||||
/-- Returns the internalized `False` constant. -/
|
||||
def getFalseExpr : SymM Expr := return (← getSharedExprs).falseExpr
|
||||
/-- Returns `true` if `e` is the internalized `False` expression. -/
|
||||
def isFalseExpr (e : Expr) : SymM Bool := return isSameExpr e (← getFalseExpr)
|
||||
/-- Returns the internalized `Bool.true`. -/
|
||||
def getBoolTrueExpr : SymM Expr := return (← getSharedExprs).btrueExpr
|
||||
/-- Returns the internalized `Bool.false`. -/
|
||||
def getBoolFalseExpr : SymM Expr := return (← getSharedExprs).bfalseExpr
|
||||
/-- Returns the internalized `0 : Nat` numeral. -/
|
||||
def getNatZeroExpr : SymM Expr := return (← getSharedExprs).natZExpr
|
||||
/-- Returns the internalized `Ordering.eq`. -/
|
||||
def getOrderingEqExpr : SymM Expr := return (← getSharedExprs).ordEqExpr
|
||||
/-- Returns the internalized `Int`. -/
|
||||
def getIntExpr : SymM Expr := return (← getSharedExprs).intExpr
|
||||
|
||||
/--
|
||||
Applies hash-consing to `e`. Recall that all expressions in a `grind` goal have
|
||||
|
||||
@@ -6,13 +6,56 @@ Authors: Leonardo de Moura
|
||||
module
|
||||
prelude
|
||||
public import Lean.Meta.Sym.SymM
|
||||
public import Lean.Meta.Transform
|
||||
import Init.Grind.Util
|
||||
import Lean.Meta.WHNF
|
||||
import Lean.Util.ForEachExpr
|
||||
namespace Lean.Meta.Sym
|
||||
open Grind
|
||||
|
||||
/--
|
||||
Returns `true` if `declName` is the name of a grind helper declaration that
|
||||
should not be unfolded by `unfoldReducible`.
|
||||
-/
|
||||
def isGrindGadget (declName : Name) : Bool :=
|
||||
declName == ``Grind.EqMatch
|
||||
|
||||
/--
|
||||
Auxiliary function for implementing `unfoldReducible` and `unfoldReducibleSimproc`.
|
||||
Performs a single step.
|
||||
-/
|
||||
public def unfoldReducibleStep (e : Expr) : MetaM TransformStep := do
|
||||
let .const declName _ := e.getAppFn | return .continue
|
||||
unless (← isReducible declName) do return .continue
|
||||
if isGrindGadget declName then return .continue
|
||||
-- See comment at isUnfoldReducibleTarget.
|
||||
if (← getEnv).isProjectionFn declName then return .continue
|
||||
let some v ← unfoldDefinition? e | return .continue
|
||||
return .visit v
|
||||
|
||||
def isUnfoldReducibleTarget (e : Expr) : CoreM Bool := do
|
||||
let env ← getEnv
|
||||
return Option.isSome <| e.find? fun e => Id.run do
|
||||
let .const declName _ := e | return false
|
||||
if getReducibilityStatusCore env declName matches .reducible then
|
||||
-- Remark: it is wasteful to unfold projection functions since
|
||||
-- kernel projections are folded again in the `foldProjs` preprocessing step.
|
||||
return !isGrindGadget declName && !env.isProjectionFn declName
|
||||
else
|
||||
return false
|
||||
|
||||
/--
|
||||
Unfolds all `reducible` declarations occurring in `e`.
|
||||
This is meant as a preprocessing step. It does **not** guarantee maximally shared terms
|
||||
-/
|
||||
public def unfoldReducible (e : Expr) : MetaM Expr := do
|
||||
if !(← isUnfoldReducibleTarget e) then return e
|
||||
Meta.transform e (pre := unfoldReducibleStep)
|
||||
|
||||
/--
|
||||
Instantiates metavariables, unfold reducible, and applies `shareCommon`.
|
||||
-/
|
||||
def preprocessExpr (e : Expr) : SymM Expr := do
|
||||
shareCommon (← instantiateMVars e)
|
||||
shareCommon (← unfoldReducible (← instantiateMVars e))
|
||||
|
||||
/--
|
||||
Helper function that removes gaps, instantiate metavariables, and applies `shareCommon`.
|
||||
@@ -32,6 +75,7 @@ def preprocessLCtx (lctx : LocalContext) : SymM LocalContext := do
|
||||
let type ← preprocessExpr type
|
||||
let value ← preprocessExpr value
|
||||
pure <| LocalDecl.ldecl index fvarId userName type value nondep kind
|
||||
index := index + 1
|
||||
decls := decls.push (some decl)
|
||||
fvarIdToDecl := fvarIdToDecl.insert decl.fvarId decl
|
||||
return { fvarIdToDecl, decls, auxDeclToFullName }
|
||||
@@ -48,4 +92,21 @@ public def preprocessMVar (mvarId : MVarId) : SymM MVarId := do
|
||||
mvarId.assign mvarNew
|
||||
return mvarNew.mvarId!
|
||||
|
||||
/-- Debug helper: throws if any subexpression of `e` is not in the table of maximally shared terms. -/
|
||||
public def _root_.Lean.Expr.checkMaxShared (e : Expr) (msg := "") : SymM Unit := do
|
||||
e.forEach fun e => do
|
||||
if let some prev := (← get).share.set.find? { expr := e } then
|
||||
unless isSameExpr prev.expr e do
|
||||
throwNotMaxShared e
|
||||
else
|
||||
throwNotMaxShared e
|
||||
where
|
||||
throwNotMaxShared (e : Expr) : SymM Unit := do
|
||||
let msg := if msg == "" then msg else s!"[{msg}] "
|
||||
throwError "{msg}term is not in the maximally shared table{indentExpr e}"
|
||||
|
||||
/-- Debug helper: throws if any subexpression of the goal's target type is not in the table of maximally shared. -/
|
||||
public def _root_.Lean.MVarId.checkMaxShared (mvarId : MVarId) (msg := "") : SymM Unit := do
|
||||
(← mvarId.getDecl).type.checkMaxShared msg
|
||||
|
||||
end Lean.Meta.Sym
|
||||
|
||||
@@ -658,7 +658,6 @@ partial def buildInductionBody (toErase toClear : Array FVarId) (goal : Expr)
|
||||
return mkApp4 (mkConst ``Bool.dcond [u]) goal c' t' f'
|
||||
| _ =>
|
||||
|
||||
|
||||
-- Check for unreachable cases. We look for the kind of expressions that `by contradiction`
|
||||
-- produces
|
||||
if e.isAppOf ``False.elim && 1 < e.getAppNumArgs then
|
||||
@@ -846,7 +845,7 @@ where doRealize (inductName : Name) := do
|
||||
throwError "Function {name} defined via WellFounded.fix with unexpected arity {funBody.getAppNumArgs}:{indentExpr funBody}"
|
||||
else
|
||||
throwError "Function {name} not defined via WellFounded.fix:{indentExpr funBody}"
|
||||
check e'
|
||||
|
||||
let (body', mvars) ← M2.run do
|
||||
forallTelescope (← inferType e').bindingDomain! fun xs goal => do
|
||||
if xs.size ≠ 2 then
|
||||
@@ -876,10 +875,6 @@ where doRealize (inductName : Name) := do
|
||||
let e' ← instantiateMVars e'
|
||||
return (e', paramMask)
|
||||
|
||||
unless (← isTypeCorrect e') do
|
||||
logError m!"failed to derive a type-correct induction principle:{indentExpr e'}"
|
||||
check e'
|
||||
|
||||
let eTyp ← inferType e'
|
||||
let eTyp ← elimTypeAnnotations eTyp
|
||||
let eTyp ← letToHave eTyp
|
||||
@@ -1066,13 +1061,9 @@ where doRealize inductName := do
|
||||
let value ← mkLambdaFVars alts value
|
||||
let value ← mkLambdaFVars motives value
|
||||
let value ← mkLambdaFVars params value
|
||||
check value
|
||||
let value ← cleanPackedArgs eqnInfo value
|
||||
return value
|
||||
|
||||
unless ← isTypeCorrect value do
|
||||
logError m!"final term is type incorrect:{indentExpr value}"
|
||||
check value
|
||||
let type ← inferType value
|
||||
let type ← elimOptParam type
|
||||
let type ← letToHave type
|
||||
@@ -1302,10 +1293,6 @@ where doRealize inductName := do
|
||||
trace[Meta.FunInd] "complete body of mutual induction principle:{indentExpr e'}"
|
||||
pure (e', paramMask, motiveArities)
|
||||
|
||||
unless (← isTypeCorrect e') do
|
||||
logError m!"constructed induction principle is not type correct:{indentExpr e'}"
|
||||
check e'
|
||||
|
||||
let eTyp ← inferType e'
|
||||
let eTyp ← elimTypeAnnotations eTyp
|
||||
let eTyp ← letToHave eTyp
|
||||
@@ -1444,9 +1431,6 @@ def deriveCases (unfolding : Bool) (name : Name) : MetaM Unit := do
|
||||
let e' ← mkLambdaFVars #[motive] e'
|
||||
mkLambdaFVarsMasked params e'
|
||||
|
||||
mapError (f := (m!"constructed functional cases principle is not type correct:{indentExpr e'}\n{indentD ·}")) do
|
||||
check e'
|
||||
|
||||
let eTyp ← inferType e'
|
||||
let eTyp ← elimTypeAnnotations eTyp
|
||||
let eTyp ← letToHave eTyp
|
||||
|
||||
@@ -11,6 +11,7 @@ import Lean.Util.ForEachExpr
|
||||
import Lean.Meta.Tactic.Grind.Util
|
||||
import Lean.Meta.Match.Basic
|
||||
import Lean.Meta.Tactic.TryThis
|
||||
import Lean.Meta.Sym.Util
|
||||
public section
|
||||
namespace Lean.Meta.Grind
|
||||
/-!
|
||||
@@ -281,7 +282,7 @@ private theorem normConfig_zetaDelta : normConfig.zetaDelta = true := rfl
|
||||
|
||||
def preprocessPattern (pat : Expr) (normalizePattern := true) : MetaM Expr := do
|
||||
let pat ← instantiateMVars pat
|
||||
let pat ← unfoldReducible pat
|
||||
let pat ← Sym.unfoldReducible pat
|
||||
let pat ← if normalizePattern then normalize pat normConfig else pure pat
|
||||
let pat ← detectOffsets pat
|
||||
let pat ← foldProjs pat
|
||||
|
||||
@@ -107,13 +107,6 @@ private def discharge? (e : Expr) : SimpM (Option Expr) := do
|
||||
open Sym
|
||||
|
||||
def GrindM.run (x : GrindM α) (params : Params) (evalTactic? : Option EvalTactic := none) : MetaM α := Sym.SymM.run do
|
||||
let falseExpr ← share <| mkConst ``False
|
||||
let trueExpr ← share <| mkConst ``True
|
||||
let bfalseExpr ← share <| mkConst ``Bool.false
|
||||
let btrueExpr ← share <| mkConst ``Bool.true
|
||||
let natZExpr ← share <| mkNatLit 0
|
||||
let ordEqExpr ← share <| mkConst ``Ordering.eq
|
||||
let intExpr ← share <| Int.mkType
|
||||
/- **Note**: Consider using `Sym.simp` in the future. -/
|
||||
let simprocs := params.normProcs
|
||||
let simpMethods := Simp.mkMethods simprocs discharge? (wellBehavedDischarge := true)
|
||||
@@ -124,9 +117,7 @@ def GrindM.run (x : GrindM α) (params : Params) (evalTactic? : Option EvalTacti
|
||||
let anchorRefs? := params.anchorRefs?
|
||||
let debug := grind.debug.get (← getOptions)
|
||||
x (← mkMethods evalTactic?).toMethodsRef
|
||||
{ config, anchorRefs?, simpMethods, simp, extensions, symPrios
|
||||
trueExpr, falseExpr, natZExpr, btrueExpr, bfalseExpr, ordEqExpr, intExpr
|
||||
debug }
|
||||
{ config, anchorRefs?, simpMethods, simp, extensions, symPrios, debug }
|
||||
|>.run' {}
|
||||
|
||||
private def mkCleanState (mvarId : MVarId) : GrindM Clean.State := mvarId.withContext do
|
||||
@@ -155,7 +146,7 @@ private def initENodeCore (e : Expr) (interpreted ctor : Bool) : GoalM Unit := d
|
||||
mkENodeCore e interpreted ctor (generation := 0) (funCC := false)
|
||||
|
||||
/-- Returns a new goal for the given metavariable. -/
|
||||
public def mkGoal (mvarId : MVarId) : GrindM Goal := do
|
||||
public def mkGoalCore (mvarId : MVarId) : GrindM Goal := do
|
||||
let config ← getConfig
|
||||
let mvarId ← if config.clean then mvarId.exposeNames else pure mvarId
|
||||
let trueExpr ← getTrueExpr
|
||||
@@ -288,7 +279,7 @@ private def initCore (mvarId : MVarId) : GrindM Goal := do
|
||||
let mvarId ← mvarId.unfoldReducible
|
||||
let mvarId ← mvarId.betaReduce
|
||||
appendTagSuffix mvarId `grind
|
||||
let goal ← mkGoal mvarId
|
||||
let goal ← mkGoalCore mvarId
|
||||
if config.revert then
|
||||
return goal
|
||||
else
|
||||
|
||||
@@ -8,6 +8,7 @@ prelude
|
||||
public import Lean.Meta.Tactic.Grind.Types
|
||||
import Init.Grind.Util
|
||||
import Lean.Meta.Sym.ExprPtr
|
||||
import Lean.Meta.Sym.Util
|
||||
import Lean.Meta.Tactic.Grind.Util
|
||||
public section
|
||||
namespace Lean.Meta.Grind
|
||||
@@ -103,7 +104,7 @@ where
|
||||
-/
|
||||
/- We must also apply beta-reduction to improve the effectiveness of the congruence closure procedure. -/
|
||||
let e ← Core.betaReduce e
|
||||
let e ← unfoldReducible e
|
||||
let e ← Sym.unfoldReducible e
|
||||
/- We must mask proofs occurring in `prop` too. -/
|
||||
let e ← visit e
|
||||
let e ← eraseIrrelevantMData e
|
||||
|
||||
@@ -11,6 +11,7 @@ public import Lean.Meta.Tactic.Grind.Types
|
||||
import Lean.Meta.Tactic.Grind.Util
|
||||
import Lean.Meta.Tactic.Grind.MatchDiscrOnly
|
||||
import Lean.Meta.Tactic.Grind.MarkNestedSubsingletons
|
||||
import Lean.Meta.Sym.Util
|
||||
public section
|
||||
namespace Lean.Meta.Grind
|
||||
|
||||
@@ -57,7 +58,7 @@ def preprocessImpl (e : Expr) : GoalM Simp.Result := do
|
||||
let e' ← instantiateMVars r.expr
|
||||
-- Remark: `simpCore` unfolds reducible constants, but it does not consistently visit all possible subterms.
|
||||
-- So, we must use the following `unfoldReducible` step. It is non-op in most cases
|
||||
let e' ← unfoldReducible e'
|
||||
let e' ← Sym.unfoldReducible e'
|
||||
let e' ← abstractNestedProofs e'
|
||||
let e' ← markNestedSubsingletons e'
|
||||
let e' ← eraseIrrelevantMData e'
|
||||
@@ -97,6 +98,6 @@ but ensures assumptions made by `grind` are satisfied.
|
||||
-/
|
||||
def preprocessLight (e : Expr) : GoalM Expr := do
|
||||
let e ← instantiateMVars e
|
||||
shareCommon (← canon (← normalizeLevels (← foldProjs (← eraseIrrelevantMData (← markNestedSubsingletons (← unfoldReducible e))))))
|
||||
shareCommon (← canon (← normalizeLevels (← foldProjs (← eraseIrrelevantMData (← markNestedSubsingletons (← Sym.unfoldReducible e))))))
|
||||
|
||||
end Lean.Meta.Grind
|
||||
|
||||
@@ -14,6 +14,7 @@ import Lean.Meta.Tactic.Grind.Arith.Simproc
|
||||
import Lean.Meta.Tactic.Simp.BuiltinSimprocs.List
|
||||
import Lean.Meta.Tactic.Simp.BuiltinSimprocs.Core
|
||||
import Lean.Meta.Tactic.Grind.Util
|
||||
import Lean.Meta.Sym.Util
|
||||
import Init.Grind.Norm
|
||||
public section
|
||||
namespace Lean.Meta.Grind
|
||||
@@ -136,7 +137,7 @@ builtin_simproc_decl reduceCtorEqCheap (_ = _) := fun e => do
|
||||
return .done { expr := mkConst ``False, proof? := (← withDefault <| mkEqFalse' (← mkLambdaFVars #[h] (← mkNoConfusion (mkConst ``False) h))) }
|
||||
|
||||
builtin_dsimproc_decl unfoldReducibleSimproc (_) := fun e => do
|
||||
unfoldReducibleStep e
|
||||
Sym.unfoldReducibleStep e
|
||||
|
||||
/-- Returns the array of simprocs used by `grind`. -/
|
||||
protected def getSimprocs : MetaM (Array Simprocs) := do
|
||||
|
||||
@@ -160,15 +160,10 @@ structure Context where
|
||||
/-- Symbol priorities for inferring E-matching patterns -/
|
||||
symPrios : SymbolPriorities
|
||||
extensions : ExtensionStateArray := #[]
|
||||
trueExpr : Expr
|
||||
falseExpr : Expr
|
||||
natZExpr : Expr
|
||||
btrueExpr : Expr
|
||||
bfalseExpr : Expr
|
||||
ordEqExpr : Expr -- `Ordering.eq`
|
||||
intExpr : Expr -- `Int`
|
||||
debug : Bool -- Cached `grind.debug (← getOptions)`
|
||||
|
||||
export Sym (getTrueExpr getFalseExpr getBoolTrueExpr getBoolFalseExpr getNatZeroExpr getOrderingEqExpr getIntExpr isTrueExpr isFalseExpr)
|
||||
|
||||
/-- Key for the congruence theorem cache. -/
|
||||
structure CongrTheoremCacheKey where
|
||||
f : Expr
|
||||
@@ -305,34 +300,6 @@ abbrev withGTransparency [MonadControlT MetaM n] [MonadLiftT GrindM n] [Monad n]
|
||||
let m := if (← getConfig).reducible then .reducible else .default
|
||||
withTransparency m k
|
||||
|
||||
/-- Returns the internalized `True` constant. -/
|
||||
def getTrueExpr : GrindM Expr := do
|
||||
return (← readThe Context).trueExpr
|
||||
|
||||
/-- Returns the internalized `False` constant. -/
|
||||
def getFalseExpr : GrindM Expr := do
|
||||
return (← readThe Context).falseExpr
|
||||
|
||||
/-- Returns the internalized `Bool.true`. -/
|
||||
def getBoolTrueExpr : GrindM Expr := do
|
||||
return (← readThe Context).btrueExpr
|
||||
|
||||
/-- Returns the internalized `Bool.false`. -/
|
||||
def getBoolFalseExpr : GrindM Expr := do
|
||||
return (← readThe Context).bfalseExpr
|
||||
|
||||
/-- Returns the internalized `0 : Nat` numeral. -/
|
||||
def getNatZeroExpr : GrindM Expr := do
|
||||
return (← readThe Context).natZExpr
|
||||
|
||||
/-- Returns the internalized `Ordering.eq`. -/
|
||||
def getOrderingEqExpr : GrindM Expr := do
|
||||
return (← readThe Context).ordEqExpr
|
||||
|
||||
/-- Returns the internalized `Int`. -/
|
||||
def getIntExpr : GrindM Expr := do
|
||||
return (← readThe Context).intExpr
|
||||
|
||||
/-- Returns the anchor references (if any) being used to restrict the search. -/
|
||||
def getAnchorRefs : GrindM (Option (Array AnchorRef)) := do
|
||||
return (← readThe Context).anchorRefs?
|
||||
@@ -412,14 +379,6 @@ Abstracts nested proofs in `e`. This is a preprocessing step performed before in
|
||||
def abstractNestedProofs (e : Expr) : GrindM Expr :=
|
||||
Meta.abstractNestedProofs e
|
||||
|
||||
/-- Returns `true` if `e` is the internalized `True` expression. -/
|
||||
def isTrueExpr (e : Expr) : GrindM Bool :=
|
||||
return isSameExpr e (← getTrueExpr)
|
||||
|
||||
/-- Returns `true` if `e` is the internalized `False` expression. -/
|
||||
def isFalseExpr (e : Expr) : GrindM Bool :=
|
||||
return isSameExpr e (← getFalseExpr)
|
||||
|
||||
/--
|
||||
Creates a congruence theorem for a `f`-applications with `numArgs` arguments.
|
||||
-/
|
||||
@@ -1148,11 +1107,11 @@ def getGeneration (e : Expr) : GoalM Nat :=
|
||||
|
||||
/-- Returns `true` if `e` is in the equivalence class of `True`. -/
|
||||
def isEqTrue (e : Expr) : GoalM Bool := do
|
||||
return isSameExpr (← getENode e).root (← getTrueExpr)
|
||||
return (← isTrueExpr (← getENode e).root)
|
||||
|
||||
/-- Returns `true` if `e` is in the equivalence class of `False`. -/
|
||||
def isEqFalse (e : Expr) : GoalM Bool := do
|
||||
return isSameExpr (← getENode e).root (← getFalseExpr)
|
||||
return (← isFalseExpr (← getENode e).root)
|
||||
|
||||
/-- Returns `true` if `e` is in the equivalence class of `Bool.true`. -/
|
||||
def isEqBoolTrue (e : Expr) : GoalM Bool := do
|
||||
|
||||
@@ -11,6 +11,7 @@ import Lean.ProjFns
|
||||
import Lean.Meta.WHNF
|
||||
import Lean.Meta.AbstractNestedProofs
|
||||
import Lean.Meta.Tactic.Clear
|
||||
import Lean.Meta.Sym.Util
|
||||
public section
|
||||
namespace Lean.Meta.Grind
|
||||
/--
|
||||
@@ -55,49 +56,11 @@ def _root_.Lean.MVarId.transformTarget (mvarId : MVarId) (f : Expr → MetaM Exp
|
||||
mvarId.assign mvarNew
|
||||
return mvarNew.mvarId!
|
||||
|
||||
/--
|
||||
Returns `true` if `declName` is the name of a grind helper declaration that
|
||||
should not be unfolded by `unfoldReducible`.
|
||||
-/
|
||||
def isGrindGadget (declName : Name) : Bool :=
|
||||
declName == ``Grind.EqMatch
|
||||
|
||||
def isUnfoldReducibleTarget (e : Expr) : CoreM Bool := do
|
||||
let env ← getEnv
|
||||
return Option.isSome <| e.find? fun e => Id.run do
|
||||
let .const declName _ := e | return false
|
||||
if getReducibilityStatusCore env declName matches .reducible then
|
||||
-- Remark: it is wasteful to unfold projection functions since
|
||||
-- kernel projections are folded again in the `foldProjs` preprocessing step.
|
||||
return !isGrindGadget declName && !env.isProjectionFn declName
|
||||
else
|
||||
return false
|
||||
|
||||
/--
|
||||
Auxiliary function for implementing `unfoldReducible` and `unfoldReducibleSimproc`.
|
||||
Performs a single step.
|
||||
-/
|
||||
def unfoldReducibleStep (e : Expr) : MetaM TransformStep := do
|
||||
let .const declName _ := e.getAppFn | return .continue
|
||||
unless (← isReducible declName) do return .continue
|
||||
if isGrindGadget declName then return .continue
|
||||
-- See comment at isUnfoldReducibleTarget.
|
||||
if (← getEnv).isProjectionFn declName then return .continue
|
||||
let some v ← unfoldDefinition? e | return .continue
|
||||
return .visit v
|
||||
|
||||
/--
|
||||
Unfolds all `reducible` declarations occurring in `e`.
|
||||
-/
|
||||
def unfoldReducible (e : Expr) : MetaM Expr := do
|
||||
if !(← isUnfoldReducibleTarget e) then return e
|
||||
Meta.transform e (pre := unfoldReducibleStep)
|
||||
|
||||
/--
|
||||
Unfolds all `reducible` declarations occurring in the goal's target.
|
||||
-/
|
||||
def _root_.Lean.MVarId.unfoldReducible (mvarId : MVarId) : MetaM MVarId :=
|
||||
mvarId.transformTarget Grind.unfoldReducible
|
||||
mvarId.transformTarget Sym.unfoldReducible
|
||||
|
||||
/--
|
||||
Beta-reduces the goal's target.
|
||||
|
||||
@@ -188,6 +188,12 @@ def applyEqLemma (e : Expr → EqResult) (lemmaName : Name) (args : Array Expr)
|
||||
return .some (e (mkAppN (mkConst lemmaName) args))
|
||||
|
||||
def reduceNatEqExpr (x y : Expr) : SimpM (Option EqResult):= do
|
||||
/-
|
||||
**TODO**: These proofs rely too much on definitional equality.
|
||||
Example:
|
||||
`x + 1 + 1 + ... + 1 = x + 1 + ... + 1`
|
||||
It will treat both sides as `x + n = x + n`.
|
||||
-/
|
||||
let some xno ← NatOffset.fromExpr? x | return none
|
||||
let some yno ← NatOffset.fromExpr? y | return none
|
||||
match xno, yno with
|
||||
|
||||
@@ -54,7 +54,7 @@ def externEntry := leading_parser
|
||||
nonReservedSymbol "extern" >> many (ppSpace >> externEntry)
|
||||
|
||||
/--
|
||||
Declare this tactic to be an alias or alternative form of an existing tactic.
|
||||
Declares this tactic to be an alias or alternative form of an existing tactic.
|
||||
|
||||
This has the following effects:
|
||||
* The alias relationship is saved
|
||||
@@ -64,13 +64,26 @@ This has the following effects:
|
||||
"tactic_alt" >> ppSpace >> ident
|
||||
|
||||
/--
|
||||
Add one or more tags to a tactic.
|
||||
Adds one or more tags to a tactic.
|
||||
|
||||
Tags should be applied to the canonical names for tactics.
|
||||
-/
|
||||
@[builtin_attr_parser] def «tactic_tag» := leading_parser
|
||||
"tactic_tag" >> many1 (ppSpace >> ident)
|
||||
|
||||
/--
|
||||
Sets the tactic's name.
|
||||
|
||||
Ordinarily, tactic names are automatically set to the first token in the tactic's parser. If this
|
||||
process fails, or if the tactic's name should be multiple tokens (e.g. `let rec`), then this
|
||||
attribute can be used to provide a name.
|
||||
|
||||
The tactic's name is used in documentation as well as in completion. Thus, the name should be a
|
||||
valid prefix of the tactic's syntax.
|
||||
-/
|
||||
@[builtin_attr_parser] def «tactic_name» := leading_parser
|
||||
"tactic_name" >> ppSpace >> (ident <|> strLit)
|
||||
|
||||
end Attr
|
||||
|
||||
end Lean.Parser
|
||||
|
||||
@@ -52,24 +52,7 @@ example (n : Nat) : n = n := by
|
||||
optional Term.motive >> sepBy1 Term.matchDiscr ", " >>
|
||||
" with " >> ppDedent matchAlts
|
||||
|
||||
/--
|
||||
The tactic
|
||||
```
|
||||
intro
|
||||
| pat1 => tac1
|
||||
| pat2 => tac2
|
||||
```
|
||||
is the same as:
|
||||
```
|
||||
intro x
|
||||
match x with
|
||||
| pat1 => tac1
|
||||
| pat2 => tac2
|
||||
```
|
||||
That is, `intro` can be followed by match arms and it introduces the values while
|
||||
doing a pattern match. This is equivalent to `fun` with match arms in term mode.
|
||||
-/
|
||||
@[builtin_tactic_parser] def introMatch := leading_parser
|
||||
@[builtin_tactic_parser, tactic_alt intro] def introMatch := leading_parser
|
||||
nonReservedSymbol "intro" >> matchAlts
|
||||
|
||||
builtin_initialize
|
||||
|
||||
@@ -191,12 +191,13 @@ builtin_initialize
|
||||
unless kind == AttributeKind.global do throwAttrMustBeGlobal name kind
|
||||
let `(«tactic_tag»|tactic_tag $tags*) := stx
|
||||
| throwError "Invalid `[{name}]` attribute syntax"
|
||||
|
||||
if (← getEnv).find? decl |>.isSome then
|
||||
if !(isTactic (← getEnv) decl) then
|
||||
throwErrorAt stx "`{decl}` is not a tactic"
|
||||
throwErrorAt stx "`{.ofConstName decl}` is not a tactic"
|
||||
|
||||
if let some tgt' := alternativeOfTactic (← getEnv) decl then
|
||||
throwErrorAt stx "`{decl}` is an alternative form of `{tgt'}`"
|
||||
throwErrorAt stx "`{.ofConstName decl}` is an alternative form of `{.ofConstName tgt'}`"
|
||||
|
||||
for t in tags do
|
||||
let tagName := t.getId
|
||||
@@ -271,14 +272,81 @@ where
|
||||
| [l] => " * " ++ l ++ "\n\n"
|
||||
| l::ls => " * " ++ l ++ "\n" ++ String.join (ls.map indentLine) ++ "\n\n"
|
||||
|
||||
/--
|
||||
The mapping between tactics and their custom names.
|
||||
|
||||
The first projection in each pair is the tactic name, and the second is the custom name.
|
||||
-/
|
||||
builtin_initialize tacticNameExt
|
||||
: PersistentEnvExtension
|
||||
(Name × String)
|
||||
(Name × String)
|
||||
(NameMap String) ←
|
||||
registerPersistentEnvExtension {
|
||||
mkInitial := pure {},
|
||||
addImportedFn := fun _ => pure {},
|
||||
addEntryFn := fun as (src, tgt) => as.insert src tgt,
|
||||
exportEntriesFn := fun es =>
|
||||
es.foldl (fun a src tgt => a.push (src, tgt)) #[] |>.qsort (Name.quickLt ·.1 ·.1)
|
||||
}
|
||||
|
||||
/--
|
||||
Finds the custom name assigned to `tac`, or returns `none` if there is no such custom name.
|
||||
-/
|
||||
def customTacticName [Monad m] [MonadEnv m] (tac : Name) : m (Option String) := do
|
||||
let env ← getEnv
|
||||
match env.getModuleIdxFor? tac with
|
||||
| some modIdx =>
|
||||
match (tacticNameExt.getModuleEntries env modIdx).binSearch (tac, default) (Name.quickLt ·.1 ·.1) with
|
||||
| some (_, val) => return some val
|
||||
| none => return none
|
||||
| none => return tacticNameExt.getState env |>.find? tac
|
||||
|
||||
builtin_initialize
|
||||
let name := `tactic_name
|
||||
registerBuiltinAttribute {
|
||||
name := name,
|
||||
ref := by exact decl_name%,
|
||||
add := fun decl stx kind => do
|
||||
unless kind == AttributeKind.global do throwAttrMustBeGlobal name kind
|
||||
let name ←
|
||||
match stx with
|
||||
| `(«tactic_name»|tactic_name $name:str) =>
|
||||
pure name.getString
|
||||
| `(«tactic_name»|tactic_name $name:ident) =>
|
||||
pure (name.getId.toString (escape := false))
|
||||
| _ => throwError "Invalid `[{name}]` attribute syntax"
|
||||
|
||||
if (← getEnv).find? decl |>.isSome then
|
||||
if !(isTactic (← getEnv) decl) then
|
||||
throwErrorAt stx m!"`{.ofConstName decl}` is not a tactic"
|
||||
if let some idx := (← getEnv).getModuleIdxFor? decl then
|
||||
if let some mod := (← getEnv).allImportedModuleNames[idx]? then
|
||||
throwErrorAt stx m!"`{.ofConstName decl}` is defined in `{mod}`, but custom names can only be added in the tactic's defining module."
|
||||
else
|
||||
throwErrorAt stx m!"`{.ofConstName decl}` is defined in an imported module, but custom names can only be added in the tactic's defining module."
|
||||
|
||||
if let some tgt' := alternativeOfTactic (← getEnv) decl then
|
||||
throwErrorAt stx "`{.ofConstName decl}` is an alternative form of `{.ofConstName tgt'}`"
|
||||
|
||||
if let some n ← customTacticName decl then
|
||||
throwError m!"The tactic `{.ofConstName decl}` already has the custom name `{n}`"
|
||||
|
||||
modifyEnv fun env => tacticNameExt.addEntry env (decl, name)
|
||||
|
||||
descr :=
|
||||
"Registers a custom name for a tactic. This custom name should be a prefix of the " ++
|
||||
"tactic's syntax, because it is used in completion.",
|
||||
applicationTime := .beforeElaboration
|
||||
}
|
||||
|
||||
-- Note: this error handler doesn't prevent all cases of non-tactics being added to the data
|
||||
-- structure. But the module will throw errors during elaboration, and there doesn't seem to be
|
||||
-- another way to implement this, because the category parser extension attribute runs *after* the
|
||||
-- attributes specified before a `syntax` command.
|
||||
/--
|
||||
Validates that a tactic alternative is actually a tactic and that syntax tagged as tactics are
|
||||
tactics.
|
||||
Validates that a tactic alternative is actually a tactic, that syntax tagged as tactics are
|
||||
tactics, and that syntax with tactic names are tactics.
|
||||
-/
|
||||
private def tacticDocsOnTactics : ParserAttributeHook where
|
||||
postAdd (catName declName : Name) (_builtIn : Bool) := do
|
||||
@@ -291,6 +359,8 @@ private def tacticDocsOnTactics : ParserAttributeHook where
|
||||
if let some tags := tacticTagExt.getState (← getEnv) |>.find? declName then
|
||||
if !tags.isEmpty then
|
||||
throwError m!"`{.ofConstName declName}` is not a tactic"
|
||||
if let some n := tacticNameExt.getState (← getEnv) |>.find? declName then
|
||||
throwError m!"`{MessageData.ofConstName declName}` is not a tactic, but it was assigned a tactic name `{n}`"
|
||||
|
||||
builtin_initialize
|
||||
registerParserAttributeHook tacticDocsOnTactics
|
||||
|
||||
@@ -224,17 +224,22 @@ def computeQueries
|
||||
break
|
||||
return queries
|
||||
|
||||
def importAllUnknownIdentifiersProvider : Name := `unknownIdentifiers
|
||||
def importAllUnknownIdentifiersProvider : Name := `allUnknownIdentifiers
|
||||
def importUnknownIdentifiersProvider : Name := `unknownIdentifiers
|
||||
|
||||
def mkUnknownIdentifierCodeActionData (params : CodeActionParams)
|
||||
(name := importUnknownIdentifiersProvider) : CodeActionResolveData := {
|
||||
params,
|
||||
providerName := name
|
||||
providerResultIndex := 0
|
||||
: CodeActionResolveData
|
||||
}
|
||||
|
||||
def importAllUnknownIdentifiersCodeAction (params : CodeActionParams) (kind : String) : CodeAction := {
|
||||
title := "Import all unambiguous unknown identifiers"
|
||||
kind? := kind
|
||||
data? := some <| toJson {
|
||||
params,
|
||||
providerName := importAllUnknownIdentifiersProvider
|
||||
providerResultIndex := 0
|
||||
: CodeActionResolveData
|
||||
}
|
||||
data? := some <| toJson <|
|
||||
mkUnknownIdentifierCodeActionData params importAllUnknownIdentifiersProvider
|
||||
}
|
||||
|
||||
private def mkImportText (ctx : Elab.ContextInfo) (mod : Name) :
|
||||
@@ -311,6 +316,7 @@ def handleUnknownIdentifierCodeAction
|
||||
insertion.edit
|
||||
]
|
||||
}
|
||||
data? := some <| toJson <| mkUnknownIdentifierCodeActionData params
|
||||
}
|
||||
if isExactMatch then
|
||||
hasUnambiguousImportCodeAction := true
|
||||
@@ -322,6 +328,7 @@ def handleUnknownIdentifierCodeAction
|
||||
textDocument := doc.versionedIdentifier
|
||||
edits := #[insertion.edit]
|
||||
}
|
||||
data? := some <| toJson <| mkUnknownIdentifierCodeActionData params
|
||||
}
|
||||
if hasUnambiguousImportCodeAction then
|
||||
unknownIdentifierCodeActions := unknownIdentifierCodeActions.push <|
|
||||
|
||||
@@ -597,7 +597,8 @@ def tacticCompletion
|
||||
(completionInfoPos : Nat)
|
||||
(ctx : ContextInfo)
|
||||
: IO (Array ResolvableCompletionItem) := ctx.runMetaM .empty do
|
||||
let allTacticDocs ← Tactic.Doc.allTacticDocs
|
||||
-- Don't include tactics that are identified only by their internal parser name
|
||||
let allTacticDocs ← Tactic.Doc.allTacticDocs (includeUnnamed := false)
|
||||
let items : Array ResolvableCompletionItem := allTacticDocs.map fun tacticDoc => {
|
||||
label := tacticDoc.userName
|
||||
detail? := none
|
||||
|
||||
@@ -793,21 +793,24 @@ section MessageHandling
|
||||
rpcEncode resp st.objects |>.map (·) ({st with objects := ·})
|
||||
return some <| .pure { response? := resp, serialized := resp.compress, isComplete := true }
|
||||
| "codeAction/resolve" =>
|
||||
let jsonParams := params
|
||||
let params ← RequestM.parseRequestParams CodeAction params
|
||||
let some data := params.data?
|
||||
| throw (RequestError.invalidParams "Expected a data field on CodeAction.")
|
||||
let data ← RequestM.parseRequestParams CodeActionResolveData data
|
||||
if data.providerName != importAllUnknownIdentifiersProvider then
|
||||
return none
|
||||
return some <| ← RequestM.asTask do
|
||||
let unknownIdentifierRanges ← waitAllUnknownIdentifierMessageRanges st.doc
|
||||
if unknownIdentifierRanges.isEmpty then
|
||||
let p := toJson params
|
||||
return { response? := p, serialized := p.compress, isComplete := true }
|
||||
let action? ← handleResolveImportAllUnknownIdentifiersCodeAction? id params unknownIdentifierRanges
|
||||
let action := action?.getD params
|
||||
let action := toJson action
|
||||
return { response? := action, serialized := action.compress, isComplete := true }
|
||||
if data.providerName == importUnknownIdentifiersProvider then
|
||||
return some <| RequestTask.pure { response? := jsonParams, serialized := jsonParams.compress, isComplete := true }
|
||||
if data.providerName == importAllUnknownIdentifiersProvider then
|
||||
return some <| ← RequestM.asTask do
|
||||
let unknownIdentifierRanges ← waitAllUnknownIdentifierMessageRanges st.doc
|
||||
if unknownIdentifierRanges.isEmpty then
|
||||
let p := toJson params
|
||||
return { response? := p, serialized := p.compress, isComplete := true }
|
||||
let action? ← handleResolveImportAllUnknownIdentifiersCodeAction? id params unknownIdentifierRanges
|
||||
let action := action?.getD params
|
||||
let action := toJson action
|
||||
return { response? := action, serialized := action.compress, isComplete := true }
|
||||
return none
|
||||
| _ =>
|
||||
return none
|
||||
|
||||
|
||||
@@ -270,9 +270,9 @@ withTraceNode `isPosTrace (msg := (return m!"{ExceptToEmoji.toEmoji ·} checking
|
||||
|
||||
The `cls`, `collapsed`, and `tag` arguments are forwarded to the constructor of `TraceData`.
|
||||
-/
|
||||
@[inline]
|
||||
def withTraceNode [always : MonadAlwaysExcept ε m] [MonadLiftT BaseIO m] (cls : Name)
|
||||
(msg : Except ε α → m MessageData) (k : m α) (collapsed := true) (tag := "") : m α := do
|
||||
let _ := always.except
|
||||
let opts ← getOptions
|
||||
if !opts.hasTrace then
|
||||
return (← k)
|
||||
@@ -280,21 +280,27 @@ def withTraceNode [always : MonadAlwaysExcept ε m] [MonadLiftT BaseIO m] (cls :
|
||||
unless clsEnabled || trace.profiler.get opts do
|
||||
return (← k)
|
||||
let oldTraces ← getResetTraces
|
||||
let (res, start, stop) ← withStartStop opts <| observing k
|
||||
let aboveThresh := trace.profiler.get opts &&
|
||||
stop - start > trace.profiler.threshold.unitAdjusted opts
|
||||
unless clsEnabled || aboveThresh do
|
||||
modifyTraces (oldTraces ++ ·)
|
||||
return (← MonadExcept.ofExcept res)
|
||||
let ref ← getRef
|
||||
let mut m ← try msg res catch _ => pure m!"<exception thrown while producing trace node message>"
|
||||
let mut data := { cls, collapsed, tag }
|
||||
if trace.profiler.get opts then
|
||||
data := { data with startTime := start, stopTime := stop }
|
||||
addTraceNode oldTraces data ref m
|
||||
MonadExcept.ofExcept res
|
||||
let resStartStop ← withStartStop opts <| let _ := always.except; observing k
|
||||
postCallback opts clsEnabled oldTraces msg resStartStop
|
||||
where
|
||||
postCallback (opts : Options) (clsEnabled oldTraces msg resStartStop) : m α := do
|
||||
let _ := always.except
|
||||
let (res, start, stop) := resStartStop
|
||||
let aboveThresh := trace.profiler.get opts &&
|
||||
stop - start > trace.profiler.threshold.unitAdjusted opts
|
||||
unless clsEnabled || aboveThresh do
|
||||
modifyTraces (oldTraces ++ ·)
|
||||
return (← MonadExcept.ofExcept res)
|
||||
let ref ← getRef
|
||||
let mut m ← try msg res catch _ => pure m!"<exception thrown while producing trace node message>"
|
||||
let mut data := { cls, collapsed, tag }
|
||||
if trace.profiler.get opts then
|
||||
data := { data with startTime := start, stopTime := stop }
|
||||
addTraceNode oldTraces data ref m
|
||||
MonadExcept.ofExcept res
|
||||
|
||||
/-- A version of `Lean.withTraceNode` which allows generating the message within the computation. -/
|
||||
@[inline]
|
||||
def withTraceNode' [MonadAlwaysExcept Exception m] [MonadLiftT BaseIO m] (cls : Name)
|
||||
(k : m (α × MessageData)) (collapsed := true) (tag := "") : m α :=
|
||||
let msg := fun
|
||||
@@ -380,10 +386,10 @@ the result produced by `k` into an emoji (e.g., `💥️`, `✅️`, `❌️`).
|
||||
|
||||
TODO: find better name for this function.
|
||||
-/
|
||||
@[inline]
|
||||
def withTraceNodeBefore [MonadRef m] [AddMessageContext m] [MonadOptions m]
|
||||
[always : MonadAlwaysExcept ε m] [MonadLiftT BaseIO m] [ExceptToEmoji ε α] (cls : Name)
|
||||
(msg : Unit → m MessageData) (k : m α) (collapsed := true) (tag := "") : m α := do
|
||||
let _ := always.except
|
||||
let opts ← getOptions
|
||||
if !opts.hasTrace then
|
||||
return (← k)
|
||||
@@ -394,18 +400,23 @@ def withTraceNodeBefore [MonadRef m] [AddMessageContext m] [MonadOptions m]
|
||||
let ref ← getRef
|
||||
-- make sure to preserve context *before* running `k`
|
||||
let msg ← withRef ref do addMessageContext (← msg ())
|
||||
let (res, start, stop) ← withStartStop opts <| observing k
|
||||
let aboveThresh := trace.profiler.get opts &&
|
||||
stop - start > trace.profiler.threshold.unitAdjusted opts
|
||||
unless clsEnabled || aboveThresh do
|
||||
modifyTraces (oldTraces ++ ·)
|
||||
return (← MonadExcept.ofExcept res)
|
||||
let mut msg := m!"{ExceptToEmoji.toEmoji res} {msg}"
|
||||
let mut data := { cls, collapsed, tag }
|
||||
if trace.profiler.get opts then
|
||||
data := { data with startTime := start, stopTime := stop }
|
||||
addTraceNode oldTraces data ref msg
|
||||
MonadExcept.ofExcept res
|
||||
let resStartStop ← withStartStop opts <| let _ := always.except; observing k
|
||||
postCallback opts clsEnabled oldTraces ref msg resStartStop
|
||||
where
|
||||
postCallback (opts : Options) (clsEnabled oldTraces ref msg resStartStop) : m α := do
|
||||
let _ := always.except
|
||||
let (res, start, stop) := resStartStop
|
||||
let aboveThresh := trace.profiler.get opts &&
|
||||
stop - start > trace.profiler.threshold.unitAdjusted opts
|
||||
unless clsEnabled || aboveThresh do
|
||||
modifyTraces (oldTraces ++ ·)
|
||||
return (← MonadExcept.ofExcept res)
|
||||
let mut msg := m!"{ExceptToEmoji.toEmoji res} {msg}"
|
||||
let mut data := { cls, collapsed, tag }
|
||||
if trace.profiler.get opts then
|
||||
data := { data with startTime := start, stopTime := stop }
|
||||
addTraceNode oldTraces data ref msg
|
||||
MonadExcept.ofExcept res
|
||||
|
||||
def addTraceAsMessages [Monad m] [MonadRef m] [MonadLog m] [MonadTrace m] : m Unit := do
|
||||
if trace.profiler.output.get? (← getOptions) |>.isSome then
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user