mirror of
https://github.com/leanprover/lean4.git
synced 2026-03-18 10:54:09 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54de6271fd | ||
|
|
eb7951e872 | ||
|
|
e601cdb193 |
@@ -1,130 +0,0 @@
|
||||
(In the following, use `sysctl -n hw.logicalcpu` instead of `nproc` on macOS)
|
||||
|
||||
## Building
|
||||
|
||||
To build Lean you should use `make -j$(nproc) -C build/release`.
|
||||
|
||||
The build uses `ccache`, and in a sandbox `ccache` may complain about read-only file systems.
|
||||
Use `CCACHE_READONLY` and `CCACHE_TEMPDIR` instead of disabling ccache completely.
|
||||
|
||||
## Running Tests
|
||||
|
||||
See `tests/README.md` for full documentation. Quick reference:
|
||||
|
||||
```bash
|
||||
# Full test suite (use after builds to verify correctness)
|
||||
CTEST_PARALLEL_LEVEL="$(nproc)" CTEST_OUTPUT_ON_FAILURE=1 \
|
||||
make -C build/release -j "$(nproc)" test
|
||||
|
||||
# Specific test by name (supports regex via ctest -R)
|
||||
CTEST_PARALLEL_LEVEL="$(nproc)" CTEST_OUTPUT_ON_FAILURE=1 \
|
||||
make -C build/release -j "$(nproc)" test ARGS='-R grind_ematch'
|
||||
|
||||
# Rerun only previously failed tests
|
||||
CTEST_PARALLEL_LEVEL="$(nproc)" CTEST_OUTPUT_ON_FAILURE=1 \
|
||||
make -C build/release -j "$(nproc)" test ARGS='--rerun-failed'
|
||||
|
||||
# Single test from tests/foo/bar/ (quick check during development)
|
||||
CTEST_PARALLEL_LEVEL="$(nproc)" CTEST_OUTPUT_ON_FAILURE=1 \
|
||||
make -C build/release -j "$(nproc)" test ARGS=-R testname'
|
||||
```
|
||||
|
||||
## Testing stage 2
|
||||
|
||||
When requested to test stage 2, build it as follows:
|
||||
```
|
||||
make -C build/release stage2 -j$(nproc)
|
||||
```
|
||||
Stage 2 is *not* automatically invalidated by changes to `src/` which allows for faster iteration
|
||||
when fixing a specific file in the stage 2 build but for invalidating any files that already passed
|
||||
the stage 2 build as well as for final validation,
|
||||
```
|
||||
make -C build/release/stage2 clean-stdlib
|
||||
```
|
||||
must be run manually before building.
|
||||
|
||||
## New features
|
||||
|
||||
When asked to implement new features:
|
||||
* begin by reviewing existing relevant code and tests
|
||||
* write comprehensive tests first (expecting that these will initially fail)
|
||||
* and then iterate on the implementation until the tests pass.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
*Never* report success on a task unless you have verified both a clean build without errors, and that the relevant tests pass.
|
||||
|
||||
## Build System Safety
|
||||
|
||||
**NEVER manually delete build directories** (build/, stage0/, stage1/, etc.) even when builds fail.
|
||||
- ONLY use the project's documented build command: `make -j$(nproc) -C build/release`
|
||||
- If a build is broken, ask the user before attempting any manual cleanup
|
||||
|
||||
## stage0 Is a Copy of src
|
||||
|
||||
**Never manually edit files under `stage0/`.** The `stage0/` directory is a snapshot of `src/` produced by `make update-stage0`. To change anything in stage0 (CMakeLists.txt, C++ source, etc.), edit the corresponding file in `src/` and let `update-stage0` propagate it.
|
||||
|
||||
## LSP and IDE Diagnostics
|
||||
|
||||
After rebuilding, LSP diagnostics may be stale until the user interacts with files. Trust command-line test results over IDE diagnostics.
|
||||
|
||||
## Update prompting when the user is frustrated
|
||||
|
||||
If the user expresses frustration with you, stop and ask them to help update this `.claude/CLAUDE.md` file with missing guidance.
|
||||
|
||||
## Creating pull requests
|
||||
|
||||
Follow the commit convention in `doc/dev/commit_convention.md`.
|
||||
|
||||
**Title format:** `<type>: <subject>` where type is one of: `feat`, `fix`, `doc`, `style`, `refactor`, `test`, `chore`, `perf`.
|
||||
Subject should use imperative present tense ("add" not "added"), no capitalization, no trailing period.
|
||||
|
||||
**Body format:** The first paragraph must start with "This PR". This paragraph is automatically incorporated into release notes. Use imperative present tense. Include motivation and contrast with previous behavior when relevant. Do NOT use markdown headings (`## Summary`, `## Test plan`, etc.) in PR bodies.
|
||||
|
||||
Example:
|
||||
```
|
||||
feat: add optional binder limit to `mkPatternFromTheorem`
|
||||
|
||||
This PR adds a `num?` parameter to `mkPatternFromTheorem` to control how many
|
||||
leading quantifiers are stripped when creating a pattern.
|
||||
```
|
||||
|
||||
**Changelog labels:** Add one `changelog-*` label to categorize the PR for release notes:
|
||||
- `changelog-language` - Language features and metaprograms
|
||||
- `changelog-tactics` - User facing tactics
|
||||
- `changelog-server` - Language server, widgets, and IDE extensions
|
||||
- `changelog-pp` - Pretty printing
|
||||
- `changelog-library` - Library
|
||||
- `changelog-compiler` - Compiler, runtime, and FFI
|
||||
- `changelog-lake` - Lake
|
||||
- `changelog-doc` - Documentation
|
||||
- `changelog-ffi` - FFI changes
|
||||
- `changelog-other` - Other changes
|
||||
- `changelog-no` - Do not include this PR in the release changelog
|
||||
|
||||
If you're unsure which label applies, it's fine to omit the label and let reviewers add it.
|
||||
|
||||
## Module System for `src/` Files
|
||||
|
||||
Files in `src/Lean/`, `src/Std/`, and `src/lake/Lake/` must have both `module` and `prelude` (CI enforces `^prelude$` on its own line). With `prelude`, nothing is auto-imported — you must explicitly import `Init.*` modules for standard library features. Check existing files in the same directory for the pattern, e.g.:
|
||||
|
||||
```lean
|
||||
module
|
||||
|
||||
prelude
|
||||
import Init.While -- needed for while/repeat
|
||||
import Init.Data.String.TakeDrop -- needed for String.startsWith
|
||||
public import Lean.Compiler.NameMangling -- public if types are used in public signatures
|
||||
```
|
||||
|
||||
Files outside these directories (e.g. `tests/`, `script/`) use just `module`.
|
||||
|
||||
## CI Log Retrieval
|
||||
|
||||
When CI jobs fail, investigate immediately - don't wait for other jobs to complete. Individual job logs are often available even while other jobs are still running. Try `gh run view <run-id> --log` or `gh run view <run-id> --log-failed`, or use `gh run view <run-id> --job=<job-id>` to target the specific failed job. Sleeping is fine when asked to monitor CI and no failures exist yet, but once any job fails, investigate that failure immediately.
|
||||
|
||||
## Copyright Headers
|
||||
|
||||
New files require a copyright header. To get the year right, always run `date +%Y` rather than relying on memory. The copyright holder should be the author or their current employer — check other recent files by the same author in the repository to determine the correct entity (e.g., "Lean FRO, LLC", "Amazon.com, Inc. or its affiliates").
|
||||
|
||||
Test files (in `tests/`) do not need copyright headers.
|
||||
@@ -1,166 +0,0 @@
|
||||
# Release Management Command
|
||||
|
||||
Execute the release process for a given version by running the release checklist and following its instructions.
|
||||
|
||||
## Before Starting
|
||||
|
||||
**IMPORTANT**: Before beginning the release process, read the in-file documentation:
|
||||
- Read `script/release_checklist.py` for what the checklist script does
|
||||
- Read `script/release_steps.py` for what the release steps script does
|
||||
|
||||
These comments explain the scripts' behavior, which repositories get special handling, and how errors are handled.
|
||||
|
||||
## Arguments
|
||||
- `version`: The version to release (e.g., v4.24.0)
|
||||
|
||||
## 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 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**
|
||||
- The checklist output will say "Run `script/release_steps.py {version} {repo_name}` to create it"
|
||||
- If a repository shows "🟡 Dependencies not ready", you CANNOT create a PR for it yet
|
||||
- You MUST rerun `release_checklist.py` before attempting to create PRs for any new repositories
|
||||
5. For each repository that the checklist says needs updating:
|
||||
- Run `script/release_steps.py {version} {repo_name}` to create the PR
|
||||
- Mark it complete when the PR is created
|
||||
6. After creating PRs, notify the user which PRs need review and merging
|
||||
7. **MANDATORY: Rerun `release_checklist.py` to check current status**
|
||||
- Do this after creating each batch of PRs
|
||||
- Do this after the user reports PRs have been merged
|
||||
- NEVER assume a repository is ready without checking the checklist output
|
||||
8. As PRs are merged and tagged, dependent repositories will become ready
|
||||
9. Continue the cycle: run checklist → create PRs for ready repos → wait for merges → repeat
|
||||
10. Continue until all repositories are updated and the release is complete
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **NEVER merge PRs autonomously** - always wait for the user to merge PRs themselves
|
||||
- The `release_steps.py` script is idempotent - it's safe to rerun
|
||||
- The `release_checklist.py` script is idempotent - it's safe to rerun
|
||||
- Some repositories depend on others (e.g., mathlib4 depends on batteries, aesop, etc.)
|
||||
- Wait for user to merge PRs before dependent repos can be updated
|
||||
- Alert user if anything unusual or scary happens
|
||||
- Use appropriate timeouts for long-running builds (verso can take 10+ minutes)
|
||||
- ProofWidgets4 uses semantic versioning (v0.0.X) - it's okay to create and push the next sequential tag yourself when needed for a release
|
||||
|
||||
## PR Status Reporting
|
||||
|
||||
Every time you run `release_checklist.py`, you MUST:
|
||||
1. Parse the output to identify ALL open PRs mentioned (lines with "✅ PR with title ... exists")
|
||||
2. Provide a summary to the user listing ALL open PRs that need review
|
||||
3. Group them by status:
|
||||
- PRs for repositories that are blocked by dependencies (show these but note they're blocked)
|
||||
- PRs for repositories that are ready to merge (highlight these)
|
||||
4. Format the summary clearly with PR numbers and URLs
|
||||
|
||||
This summary should be provided EVERY time you run the checklist, not just after creating new PRs.
|
||||
The user needs to see the complete picture of what's waiting for review.
|
||||
|
||||
## Checking PR Status When Asked
|
||||
|
||||
When the user asks for "status" or you need to report on PRs between checklist runs:
|
||||
- **ALWAYS check actual PR state** using `gh pr view <number> --repo <repo> --json state,mergedAt`
|
||||
- Do NOT rely on cached CI results or previous checklist output
|
||||
- The user may have merged PRs since your last check
|
||||
- Report which PRs are MERGED, which are OPEN with CI status, and which are still pending
|
||||
- After discovering merged PRs, rerun `release_checklist.py` to advance the release process
|
||||
|
||||
## 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.
|
||||
|
||||
## CI Failures: Investigate Immediately
|
||||
|
||||
**CRITICAL: If the checklist reports `❌ CI: X check(s) failing` for any PR, investigate immediately.**
|
||||
|
||||
Do NOT:
|
||||
- Report it as "CI in progress" or "some checks pending"
|
||||
- Wait for the remaining checks to finish before investigating
|
||||
- Assume it's a transient failure without checking
|
||||
|
||||
DO:
|
||||
1. Run `gh pr checks <number> --repo <owner>/<repo>` to see which specific check failed
|
||||
2. Run `gh run view <run-id> --repo <owner>/<repo> --log-failed` to see the failure output
|
||||
3. Diagnose the failure and report clearly to the user: what failed and why
|
||||
4. Propose a fix if one is obvious (e.g., subverso version mismatch, transient elan install error)
|
||||
|
||||
The checklist now distinguishes `❌ X check(s) failing, Y still in progress` from `🔄 Y check(s) in progress`.
|
||||
Any `❌` in CI status requires immediate investigation — do not move on.
|
||||
|
||||
## Waiting for CI or Merges
|
||||
|
||||
Use `gh pr checks --watch` to block until a PR's CI checks complete (no polling needed).
|
||||
Run these as background bash commands so you get notified when they finish:
|
||||
|
||||
```bash
|
||||
# Watch CI, then check merge state
|
||||
gh pr checks <number> --repo <owner>/<repo> --watch && gh pr view <number> --repo <owner>/<repo> --json state --jq '.state'
|
||||
```
|
||||
|
||||
For multiple PRs, launch one background command per PR in parallel. When each completes,
|
||||
you'll be notified automatically via a task-notification. Do NOT use sleep-based polling
|
||||
loops — `--watch` is event-driven and exits as soon as checks finish.
|
||||
|
||||
Note: `gh pr checks --watch` exits as soon as ALL checks complete (pass or fail). If some checks
|
||||
fail while others are still running, `--watch` will continue until everything settles, then exit
|
||||
with a non-zero code. So a background `--watch` finishing = all checks done; check which failed.
|
||||
|
||||
## Error Handling
|
||||
|
||||
**CRITICAL**: If something goes wrong or a command fails:
|
||||
- **DO NOT** try to manually reproduce the failing steps yourself
|
||||
- **DO NOT** try to fix things by running git commands or other manual operations
|
||||
- Both scripts are idempotent and designed to handle partial completion gracefully
|
||||
- If a script continues to fail after retrying, report the error to the user and wait for instructions
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"extraKnownMarketplaces": {
|
||||
"leanprover": {
|
||||
"source": {
|
||||
"source": "github",
|
||||
"repo": "leanprover/skills"
|
||||
}
|
||||
}
|
||||
},
|
||||
"enabledPlugins": {
|
||||
"lean@leanprover": true
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: profiling
|
||||
description: Profile Lean programs with demangled names using samply and Firefox Profiler. Use when the user asks to profile a Lean binary or investigate performance.
|
||||
allowed-tools: Bash, Read, Glob, Grep
|
||||
---
|
||||
|
||||
# Profiling Lean Programs
|
||||
|
||||
Full documentation: `script/PROFILER_README.md`.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
script/lean_profile.sh ./build/release/stage1/bin/lean some_file.lean
|
||||
```
|
||||
|
||||
Requires `samply` (`cargo install samply`) and `python3`.
|
||||
|
||||
## Agent Notes
|
||||
|
||||
- The pipeline is interactive (serves to browser at the end). When running non-interactively, run the steps manually instead of using the wrapper script.
|
||||
- The three steps are: `samply record --save-only`, `symbolicate_profile.py`, then `serve_profile.py`.
|
||||
- `lean_demangle.py` works standalone as a stdin filter (like `c++filt`) for quick name lookups.
|
||||
- The `--raw` flag on `lean_demangle.py` gives exact demangled names without postprocessing (keeps `._redArg`, `._lam_0` suffixes as-is).
|
||||
- Use `PROFILE_KEEP=1` to keep the temp directory for later inspection.
|
||||
- The demangled profile is a standard Firefox Profiler JSON. Function names live in `threads[i].stringArray`, indexed by `threads[i].funcTable.name`.
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: zulip-extract
|
||||
description: Extract Zulip thread HTML dumps into readable plain text. Use when the user provides a Zulip HTML file or asks to parse/read/convert/summarize a Zulip thread.
|
||||
---
|
||||
|
||||
# Zulip Thread Extractor
|
||||
|
||||
Run the bundled script to convert a Zulip HTML page dump into plain text.
|
||||
|
||||
## Usage
|
||||
```bash
|
||||
python3 .claude/skills/zulip-extract/zulip_thread_extract.py input.html output.txt
|
||||
```
|
||||
|
||||
The script has zero dependencies beyond Python 3 stdlib.
|
||||
It extracts sender, timestamp, message content (with code blocks,
|
||||
links, quotes, mentions), and reactions.
|
||||
@@ -1,313 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Convert a Zulip HTML page dump to plain text (the visible message thread).
|
||||
|
||||
Zero external dependencies — uses only the Python standard library.
|
||||
|
||||
Usage:
|
||||
python3 zulip_thread_extract.py input.html [output.txt]
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
from html.parser import HTMLParser
|
||||
from html import unescape
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Minimal DOM built from stdlib HTMLParser
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class Node:
|
||||
"""A lightweight DOM node."""
|
||||
__slots__ = ('tag', 'attrs', 'children', 'parent', 'text')
|
||||
|
||||
def __init__(self, tag='', attrs=None):
|
||||
self.tag = tag
|
||||
self.attrs = dict(attrs) if attrs else {}
|
||||
self.children = []
|
||||
self.parent = None
|
||||
self.text = '' # for text nodes only (tag == '')
|
||||
|
||||
@property
|
||||
def cls(self):
|
||||
return self.attrs.get('class', '')
|
||||
|
||||
def has_class(self, c):
|
||||
return c in self.cls.split()
|
||||
|
||||
def find_all(self, tag=None, class_=None):
|
||||
"""Depth-first search for matching descendants."""
|
||||
for child in self.children:
|
||||
if child.tag == '':
|
||||
continue
|
||||
match = True
|
||||
if tag and child.tag != tag:
|
||||
match = False
|
||||
if class_ and not child.has_class(class_):
|
||||
match = False
|
||||
if match:
|
||||
yield child
|
||||
yield from child.find_all(tag, class_)
|
||||
|
||||
def find(self, tag=None, class_=None):
|
||||
return next(self.find_all(tag, class_), None)
|
||||
|
||||
def get_text(self):
|
||||
if self.tag == '':
|
||||
return self.text
|
||||
return ''.join(c.get_text() for c in self.children)
|
||||
|
||||
|
||||
class DOMBuilder(HTMLParser):
|
||||
"""Build a minimal DOM tree from HTML."""
|
||||
|
||||
VOID_ELEMENTS = frozenset([
|
||||
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
||||
'link', 'meta', 'param', 'source', 'track', 'wbr',
|
||||
])
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.root = Node('root')
|
||||
self._cur = self.root
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
node = Node(tag, attrs)
|
||||
node.parent = self._cur
|
||||
self._cur.children.append(node)
|
||||
if tag not in self.VOID_ELEMENTS:
|
||||
self._cur = node
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
# Walk up to find the matching open tag (tolerates misnesting)
|
||||
n = self._cur
|
||||
while n and n.tag != tag and n.parent:
|
||||
n = n.parent
|
||||
if n and n.parent:
|
||||
self._cur = n.parent
|
||||
|
||||
def handle_data(self, data):
|
||||
t = Node()
|
||||
t.text = data
|
||||
t.parent = self._cur
|
||||
self._cur.children.append(t)
|
||||
|
||||
def handle_entityref(self, name):
|
||||
self.handle_data(unescape(f'&{name};'))
|
||||
|
||||
def handle_charref(self, name):
|
||||
self.handle_data(unescape(f'&#{name};'))
|
||||
|
||||
|
||||
def parse_html(path):
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
html = f.read()
|
||||
builder = DOMBuilder()
|
||||
builder.feed(html)
|
||||
return builder.root
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Content extraction
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
SKIP_CLASSES = {
|
||||
'message_controls', 'message_length_controller',
|
||||
'code-buttons-container', 'copy_codeblock', 'code_external_link',
|
||||
'message_edit_notice', 'edit-notifications',
|
||||
}
|
||||
|
||||
def should_skip(node):
|
||||
return bool(SKIP_CLASSES & set(node.cls.split()))
|
||||
|
||||
|
||||
def extract_content(node):
|
||||
"""Recursively convert a message_content node into readable text."""
|
||||
parts = []
|
||||
for child in node.children:
|
||||
# Text node
|
||||
if child.tag == '':
|
||||
parts.append(child.text)
|
||||
continue
|
||||
|
||||
if should_skip(child):
|
||||
continue
|
||||
|
||||
cls_set = set(child.cls.split())
|
||||
|
||||
# Code block wrappers (div.codehilite / div.zulip-code-block)
|
||||
if child.tag == 'div' and ({'codehilite', 'zulip-code-block'} & cls_set):
|
||||
code = child.find('code')
|
||||
lang = child.attrs.get('data-code-language', '')
|
||||
text = code.get_text() if code else child.get_text()
|
||||
parts.append(f'\n```{lang}\n{text}```\n')
|
||||
continue
|
||||
|
||||
# <pre> (bare code blocks without wrapper div)
|
||||
if child.tag == 'pre':
|
||||
code = child.find('code')
|
||||
text = code.get_text() if code else child.get_text()
|
||||
parts.append(f'\n```\n{text}```\n')
|
||||
continue
|
||||
|
||||
# Inline <code>
|
||||
if child.tag == 'code':
|
||||
parts.append(f'`{child.get_text()}`')
|
||||
continue
|
||||
|
||||
# Paragraph
|
||||
if child.tag == 'p':
|
||||
inner = extract_content(child)
|
||||
parts.append(f'\n{inner}\n')
|
||||
continue
|
||||
|
||||
# Line break
|
||||
if child.tag == 'br':
|
||||
parts.append('\n')
|
||||
continue
|
||||
|
||||
# Links
|
||||
if child.tag == 'a':
|
||||
href = child.attrs.get('href', '')
|
||||
text = child.get_text().strip()
|
||||
if href and not href.startswith('#') and text:
|
||||
parts.append(f'[{text}]({href})')
|
||||
else:
|
||||
parts.append(text)
|
||||
continue
|
||||
|
||||
# Block quotes
|
||||
if child.tag == 'blockquote':
|
||||
bq = extract_content(child).strip()
|
||||
parts.append('\n' + '\n'.join(f'> {l}' for l in bq.split('\n')) + '\n')
|
||||
continue
|
||||
|
||||
# Lists
|
||||
if child.tag in ('ul', 'ol'):
|
||||
for i, li in enumerate(c for c in child.children if c.tag == 'li'):
|
||||
pfx = f'{i+1}.' if child.tag == 'ol' else '-'
|
||||
parts.append(f'\n{pfx} {extract_content(li).strip()}')
|
||||
parts.append('\n')
|
||||
continue
|
||||
|
||||
# User mentions
|
||||
if 'user-mention' in cls_set:
|
||||
parts.append(f'@{child.get_text().strip().lstrip("@")}')
|
||||
continue
|
||||
|
||||
# Emoji
|
||||
if 'emoji' in cls_set:
|
||||
alt = child.attrs.get('alt', '') or child.attrs.get('title', '')
|
||||
if alt:
|
||||
parts.append(alt)
|
||||
continue
|
||||
|
||||
# Recurse into everything else
|
||||
parts.append(extract_content(child))
|
||||
|
||||
return ''.join(parts)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Thread extraction
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def extract_thread(html_path, output_path=None):
|
||||
root = parse_html(html_path)
|
||||
|
||||
# Find the message list
|
||||
msg_list = root.find('div', class_='message-list')
|
||||
if not msg_list:
|
||||
print("ERROR: Could not find message list.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Topic header
|
||||
header = msg_list.find('div', class_='message_header')
|
||||
stream_name = topic_name = date_str = ''
|
||||
if header:
|
||||
el = header.find('span', class_='message-header-stream-name')
|
||||
if el: stream_name = el.get_text().strip()
|
||||
el = header.find('span', class_='stream-topic-inner')
|
||||
if el: topic_name = el.get_text().strip()
|
||||
el = header.find('span', class_='recipient_row_date')
|
||||
if el:
|
||||
tr = el.find('span', class_='timerender-content')
|
||||
if tr:
|
||||
date_str = tr.attrs.get('data-tippy-content', '') or tr.get_text().strip()
|
||||
|
||||
# Messages
|
||||
messages = []
|
||||
for row in msg_list.find_all('div', class_='message_row'):
|
||||
if not row.has_class('messagebox-includes-sender'):
|
||||
continue
|
||||
|
||||
msg = {}
|
||||
|
||||
sn = row.find('span', class_='sender_name_text')
|
||||
if sn:
|
||||
un = sn.find('span', class_='user-name')
|
||||
msg['sender'] = (un or sn).get_text().strip()
|
||||
|
||||
tm = row.find('a', class_='message-time')
|
||||
if tm:
|
||||
msg['time'] = tm.get_text().strip()
|
||||
|
||||
cd = row.find('div', class_='message_content')
|
||||
if cd:
|
||||
text = extract_content(cd)
|
||||
text = re.sub(r'\n{3,}', '\n\n', text).strip()
|
||||
msg['content'] = text
|
||||
|
||||
# Reactions
|
||||
reactions = []
|
||||
for rx in row.find_all('div', class_='message_reaction'):
|
||||
em = rx.find('div', class_='emoji_alt_code')
|
||||
if em:
|
||||
reactions.append(em.get_text().strip())
|
||||
else:
|
||||
img = rx.find(tag='img')
|
||||
if img:
|
||||
reactions.append(img.attrs.get('alt', ''))
|
||||
cnt = rx.find('span', class_='message_reaction_count')
|
||||
if cnt and reactions:
|
||||
c = cnt.get_text().strip()
|
||||
if c and c != '1':
|
||||
reactions[-1] += f' x{c}'
|
||||
if reactions:
|
||||
msg['reactions'] = reactions
|
||||
|
||||
if msg.get('content') or msg.get('sender'):
|
||||
messages.append(msg)
|
||||
|
||||
# Format
|
||||
lines = [
|
||||
'=' * 70,
|
||||
f'# {stream_name} > {topic_name}',
|
||||
]
|
||||
if date_str:
|
||||
lines.append(f'# Started: {date_str}')
|
||||
lines += [f'# Messages: {len(messages)}', '=' * 70, '']
|
||||
|
||||
for msg in messages:
|
||||
lines.append(f'--- {msg.get("sender","?")} [{msg.get("time","")}] ---')
|
||||
lines.append(msg.get('content', ''))
|
||||
if msg.get('reactions'):
|
||||
lines.append(f' Reactions: {", ".join(msg["reactions"])}')
|
||||
lines.append('')
|
||||
|
||||
result = '\n'.join(lines)
|
||||
if output_path:
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(result)
|
||||
print(f"Written {len(messages)} messages to {output_path}")
|
||||
else:
|
||||
print(result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
print(f"Usage: {sys.argv[0]} input.html [output.txt]")
|
||||
sys.exit(1)
|
||||
extract_thread(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else None)
|
||||
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -4,4 +4,3 @@ RELEASES.md merge=union
|
||||
stage0/** binary linguist-generated
|
||||
# The following file is often manually edited, so do show it in diffs
|
||||
stage0/src/stdlib_flags.h -binary -linguist-generated
|
||||
doc/std/grove/GroveStdlib/Generated/** linguist-generated
|
||||
|
||||
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -9,15 +9,9 @@ assignees: ''
|
||||
|
||||
### Prerequisites
|
||||
|
||||
<!-- Please put an X between the brackets as you perform the following steps: -->
|
||||
|
||||
* [ ] Check that your issue is not already filed:
|
||||
https://github.com/leanprover/lean4/issues
|
||||
* [ ] Reduce the issue to a minimal, self-contained, reproducible test case.
|
||||
Avoid dependencies to Mathlib or Batteries.
|
||||
* [ ] Test your test case against the latest nightly release, for example on
|
||||
https://live.lean-lang.org/#project=lean-nightly
|
||||
(You can also use the settings there to switch to “Lean nightly”)
|
||||
* [ ] Put an X between the brackets on this line if you have done all of the following:
|
||||
* Check that your issue is not already [filed](https://github.com/leanprover/lean4/issues).
|
||||
* Reduce the issue to a minimal, self-contained, reproducible test case. Avoid dependencies to mathlib4 or std4.
|
||||
|
||||
### Description
|
||||
|
||||
@@ -25,7 +19,7 @@ assignees: ''
|
||||
|
||||
### Context
|
||||
|
||||
[Broader context that the issue occurred in. If there was any prior discussion on [the Lean Zulip](https://leanprover.zulipchat.com), link it here as well.]
|
||||
[Broader context that the issue occured in. If there was any prior discussion on [the Lean Zulip](https://leanprover.zulipchat.com), link it here as well.]
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
@@ -39,8 +33,8 @@ assignees: ''
|
||||
|
||||
### Versions
|
||||
|
||||
[Output of `#version` or `#eval Lean.versionString`]
|
||||
[OS version, if not using live.lean-lang.org.]
|
||||
[Output of `#eval Lean.versionString` or of `lean --version` in the folder that the issue occured in]
|
||||
[OS version]
|
||||
|
||||
### Additional Information
|
||||
|
||||
|
||||
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -5,17 +5,10 @@
|
||||
* Include the link to your `RFC` or `bug` issue in the description.
|
||||
* If the issue does not already have approval from a developer, submit the PR as draft.
|
||||
* The PR title/description will become the commit message. Keep it up-to-date as the PR evolves.
|
||||
* For `feat/fix` PRs, the first paragraph starting with "This PR" must be present and will become a
|
||||
changelog entry unless the PR is labeled with `no-changelog`. If the PR does not have this label,
|
||||
it must instead be categorized with one of the `changelog-*` labels (which will be done by a
|
||||
reviewer for external PRs).
|
||||
* A toolchain of the form `leanprover/lean4-pr-releases:pr-release-NNNN` for Linux and M-series Macs will be generated upon build. To generate binaries for Windows and Intel-based Macs as well, write a comment containing `release-ci` on its own line.
|
||||
* If you rebase your PR onto `nightly-with-mathlib` then CI will test Mathlib against your PR.
|
||||
* You can manage the `awaiting-review`, `awaiting-author`, and `WIP` labels yourself, by writing a comment containing one of these labels on its own line.
|
||||
* Remove this section, up to and including the `---` before submitting.
|
||||
|
||||
---
|
||||
|
||||
This PR <short changelog summary for feat/fix, see above>.
|
||||
|
||||
Closes <`RFC` or `bug` issue number fixed by this PR, if any>
|
||||
Closes #0000 (`RFC` or `bug` issue number fixed by this PR, if any)
|
||||
|
||||
5
.github/actionlint.yaml
vendored
5
.github/actionlint.yaml
vendored
@@ -1,5 +0,0 @@
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- nscloud-ubuntu-22.04-amd64-4x16
|
||||
- nscloud-ubuntu-22.04-amd64-8x16
|
||||
- nscloud-macos-sonoma-arm64-6x14
|
||||
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
@@ -1,8 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
commit-message:
|
||||
prefix: "chore: CI"
|
||||
4
.github/workflows/actionlint.yml
vendored
4
.github/workflows/actionlint.yml
vendored
@@ -15,8 +15,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v3
|
||||
- name: actionlint
|
||||
uses: raven-actions/actionlint@v2
|
||||
uses: raven-actions/actionlint@v1
|
||||
with:
|
||||
pyflakes: false # we do not use python scripts
|
||||
|
||||
41
.github/workflows/awaiting-manual.yml
vendored
41
.github/workflows/awaiting-manual.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: Check awaiting-manual label
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
check-awaiting-manual:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check awaiting-manual label
|
||||
id: check-awaiting-manual-label
|
||||
if: github.event_name == 'pull_request_target'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const { labels, number: prNumber } = context.payload.pull_request;
|
||||
const hasAwaiting = labels.some(label => label.name == "awaiting-manual");
|
||||
const hasBreaks = labels.some(label => label.name == "breaks-manual");
|
||||
const hasBuilds = labels.some(label => label.name == "builds-manual");
|
||||
|
||||
if (hasAwaiting && hasBreaks) {
|
||||
core.setFailed('PR has both "awaiting-manual" and "breaks-manual" labels.');
|
||||
} else if (hasAwaiting && !hasBreaks && !hasBuilds) {
|
||||
core.info('PR is marked "awaiting-manual" but neither "breaks-manual" nor "builds-manual" labels are present.');
|
||||
core.setOutput('awaiting', 'true');
|
||||
}
|
||||
|
||||
- name: Wait for manual compatibility
|
||||
if: github.event_name == 'pull_request_target' && steps.check-awaiting-manual-label.outputs.awaiting == 'true'
|
||||
run: |
|
||||
echo "::notice title=Awaiting manual::PR is marked 'awaiting-manual' but neither 'breaks-manual' nor 'builds-manual' labels are present."
|
||||
echo "This check will remain in progress until the PR is updated with appropriate manual compatibility labels."
|
||||
# Keep the job running indefinitely to show "in progress" status
|
||||
while true; do
|
||||
sleep 3600 # Sleep for 1 hour at a time
|
||||
done
|
||||
41
.github/workflows/awaiting-mathlib.yml
vendored
41
.github/workflows/awaiting-mathlib.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: Check awaiting-mathlib label
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
check-awaiting-mathlib:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check awaiting-mathlib label
|
||||
id: check-awaiting-mathlib-label
|
||||
if: github.event_name == 'pull_request_target'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const { labels, number: prNumber } = context.payload.pull_request;
|
||||
const hasAwaiting = labels.some(label => label.name == "awaiting-mathlib");
|
||||
const hasBreaks = labels.some(label => label.name == "breaks-mathlib");
|
||||
const hasBuilds = labels.some(label => label.name == "builds-mathlib");
|
||||
|
||||
if (hasAwaiting && hasBreaks) {
|
||||
core.setFailed('PR has both "awaiting-mathlib" and "breaks-mathlib" labels.');
|
||||
} else if (hasAwaiting && !hasBreaks && !hasBuilds) {
|
||||
core.info('PR is marked "awaiting-mathlib" but neither "breaks-mathlib" nor "builds-mathlib" labels are present.');
|
||||
core.setOutput('awaiting', 'true');
|
||||
}
|
||||
|
||||
- name: Wait for mathlib compatibility
|
||||
if: github.event_name == 'pull_request_target' && steps.check-awaiting-mathlib-label.outputs.awaiting == 'true'
|
||||
run: |
|
||||
echo "::notice title=Awaiting mathlib::PR is marked 'awaiting-mathlib' but neither 'breaks-mathlib' nor 'builds-mathlib' labels are present."
|
||||
echo "This check will remain in progress until the PR is updated with appropriate mathlib compatibility labels."
|
||||
# Keep the job running indefinitely to show "in progress" status
|
||||
while true; do
|
||||
sleep 3600 # Sleep for 1 hour at a time
|
||||
done
|
||||
266
.github/workflows/build-template.yml
vendored
266
.github/workflows/build-template.yml
vendored
@@ -1,266 +0,0 @@
|
||||
# instantiated by ci.yml
|
||||
name: build-template
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
config:
|
||||
type: string
|
||||
required: true
|
||||
nightly:
|
||||
type: string
|
||||
required: true
|
||||
LEAN_VERSION_MAJOR:
|
||||
type: string
|
||||
required: true
|
||||
LEAN_VERSION_MINOR:
|
||||
type: string
|
||||
required: true
|
||||
LEAN_VERSION_PATCH:
|
||||
type: string
|
||||
required: true
|
||||
LEAN_SPECIAL_VERSION_DESC:
|
||||
type: string
|
||||
required: true
|
||||
RELEASE_TAG:
|
||||
type: string
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name != 'schedule' || github.repository == 'leanprover/lean4'
|
||||
strategy:
|
||||
matrix:
|
||||
include: ${{fromJson(inputs.config)}}
|
||||
# complete all jobs
|
||||
fail-fast: false
|
||||
runs-on: ${{ endsWith(matrix.os, '-with-cache') && fromJSON(format('["{0}", "nscloud-git-mirror-1gb"]', matrix.os)) || matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.shell || 'nix develop -c bash -euxo pipefail {0}' }}
|
||||
name: ${{ matrix.name }}
|
||||
env:
|
||||
# must be inside workspace
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPRESS: true
|
||||
# current cache limit
|
||||
CCACHE_MAXSIZE: 400M
|
||||
# squelch error message about missing nixpkgs channel
|
||||
NIX_BUILD_SHELL: bash
|
||||
LSAN_OPTIONS: max_leaks=10
|
||||
# somehow MinGW clang64 (or cmake?) defaults to `g++` even though it doesn't exist
|
||||
CXX: c++
|
||||
MACOSX_DEPLOYMENT_TARGET: 11.0
|
||||
steps:
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
if: runner.os == 'Linux' && !matrix.cmultilib
|
||||
- name: Install MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: clang64
|
||||
# `:` means do not prefix with msystem
|
||||
pacboy: "make: python: cmake clang ccache gmp libuv git: zip: unzip: diffutils: binutils: tree: zstd tar:"
|
||||
if: runner.os == 'Windows'
|
||||
- name: Install Brew Packages
|
||||
run: |
|
||||
brew install ccache tree zstd coreutils gmp libuv
|
||||
if: runner.os == 'macOS'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# the default is to use a virtual merge commit between the PR and master: just use the PR
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Open Nix shell once
|
||||
run: true
|
||||
if: runner.os == 'Linux'
|
||||
# Do check out some CI-relevant files from virtual merge commit to accommodate CI changes on
|
||||
# master (as the workflow files themselves are always taken from the merge)
|
||||
# (needs to be after "Install *" to use the right shell)
|
||||
- name: CI Merge Checkout
|
||||
run: |
|
||||
git fetch --depth=1 origin ${{ github.sha }}
|
||||
git checkout FETCH_HEAD flake.nix flake.lock script/prepare-* tests/elab/importStructure.lean
|
||||
if: github.event_name == 'pull_request'
|
||||
# (needs to be after "Checkout" so files don't get overridden)
|
||||
- name: Setup emsdk
|
||||
uses: mymindstorm/setup-emsdk@v14
|
||||
with:
|
||||
version: 3.1.44
|
||||
actions-cache-folder: emsdk
|
||||
if: matrix.wasm
|
||||
- name: Install 32bit c libs
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-multilib g++-multilib ccache libuv1-dev:i386 pkgconf:i386
|
||||
if: matrix.cmultilib
|
||||
- name: Restore Cache
|
||||
id: restore-cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
# NOTE: must be in sync with `save` below and with `restore-cache` in `update-stage0.yml`
|
||||
path: |
|
||||
.ccache
|
||||
${{ matrix.name == 'Linux Lake' && 'build/stage1/**/*.trace
|
||||
build/stage1/**/*.olean*
|
||||
build/stage1/**/*.ilean
|
||||
build/stage1/**/*.ir
|
||||
build/stage1/**/*.c
|
||||
build/stage1/**/*.c.o*' || '' }}
|
||||
key: ${{ matrix.name }}-build-v4-${{ github.sha }}
|
||||
# fall back to (latest) previous cache
|
||||
restore-keys: |
|
||||
${{ matrix.name }}-build-v4
|
||||
# open nix-shell once for initial setup
|
||||
- name: Setup
|
||||
run: |
|
||||
ccache --zero-stats
|
||||
if: runner.os == 'Linux'
|
||||
- name: Set up env
|
||||
run: |
|
||||
echo "NPROC=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)" >> $GITHUB_ENV
|
||||
if ! diff src/stdlib_flags.h stage0/src/stdlib_flags.h; then
|
||||
echo "src/stdlib_flags.h and stage0/src/stdlib_flags.h differ, will test and pack stage 2"
|
||||
echo "TARGET_STAGE=stage2" >> $GITHUB_ENV
|
||||
else
|
||||
echo "TARGET_STAGE=stage1" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Build
|
||||
run: |
|
||||
ulimit -c unlimited # coredumps
|
||||
[ -d build ] || mkdir build
|
||||
cd build
|
||||
# arguments passed to `cmake`
|
||||
OPTIONS=(-DLEAN_EXTRA_MAKE_OPTS=-DwarningAsError=true)
|
||||
if [[ -n '${{ matrix.release }}' ]]; then
|
||||
# this also enables githash embedding into stage 1 library, which prohibits reusing
|
||||
# `.olean`s across commits, so we don't do it in the fast non-release CI
|
||||
OPTIONS+=(-DCHECK_OLEAN_VERSION=ON)
|
||||
fi
|
||||
if [[ -n '${{ matrix.cross_target }}' ]]; then
|
||||
# used by `prepare-llvm`
|
||||
export EXTRA_FLAGS=--target=${{ matrix.cross_target }}
|
||||
OPTIONS+=(-DLEAN_PLATFORM_TARGET=${{ matrix.cross_target }})
|
||||
fi
|
||||
if [[ -n '${{ matrix.prepare-llvm }}' ]]; then
|
||||
wget -q ${{ matrix.llvm-url }}
|
||||
PREPARE="$(${{ matrix.prepare-llvm }})"
|
||||
if [ "$TARGET_STAGE" == "stage2" ]; then
|
||||
cp -r stage1 stage2
|
||||
fi
|
||||
eval "OPTIONS+=($PREPARE)"
|
||||
fi
|
||||
if [[ -n '${{ matrix.release }}' && -n '${{ inputs.nightly }}' ]]; then
|
||||
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=${{ inputs.nightly }})
|
||||
fi
|
||||
if [[ -n '${{ matrix.release }}' && -n '${{ inputs.RELEASE_TAG }}' ]]; then
|
||||
OPTIONS+=(-DLEAN_VERSION_MAJOR=${{ inputs.LEAN_VERSION_MAJOR }})
|
||||
OPTIONS+=(-DLEAN_VERSION_MINOR=${{ inputs.LEAN_VERSION_MINOR }})
|
||||
OPTIONS+=(-DLEAN_VERSION_PATCH=${{ inputs.LEAN_VERSION_PATCH }})
|
||||
OPTIONS+=(-DLEAN_VERSION_IS_RELEASE=1)
|
||||
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=${{ inputs.LEAN_SPECIAL_VERSION_DESC }})
|
||||
fi
|
||||
# contortion to support empty OPTIONS with old macOS bash
|
||||
cmake .. --preset ${{ matrix.CMAKE_PRESET || 'release' }} -B . ${{ matrix.CMAKE_OPTIONS }} ${OPTIONS[@]+"${OPTIONS[@]}"} -DLEAN_INSTALL_PREFIX=$PWD/..
|
||||
time make $TARGET_STAGE -j$NPROC
|
||||
# Should be done as early as possible and in particular *before* "Check rebootstrap" which
|
||||
# changes the state of stage1/
|
||||
- name: Save Cache
|
||||
# Caching on cancellation created some mysterious issues perhaps related to improper build
|
||||
# shutdown
|
||||
if: steps.restore-cache.outputs.cache-hit != 'true' && !cancelled()
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
# NOTE: must be in sync with `restore` above
|
||||
path: |
|
||||
.ccache
|
||||
${{ matrix.name == 'Linux Lake' && 'build/stage1/**/*.trace
|
||||
build/stage1/**/*.olean*
|
||||
build/stage1/**/*.ilean
|
||||
build/stage1/**/*.ir
|
||||
build/stage1/**/*.c
|
||||
build/stage1/**/*.c.o*' || '' }}
|
||||
key: ${{ steps.restore-cache.outputs.cache-primary-key }}
|
||||
- name: Install
|
||||
run: |
|
||||
make -C build/$TARGET_STAGE install
|
||||
- name: Check Binaries
|
||||
run: ${{ matrix.binary-check }} lean-*/bin/* || true
|
||||
- name: Count binary symbols
|
||||
run: |
|
||||
for f in lean-*/bin/*; do
|
||||
echo "$f: $(nm $f | grep " T " | wc -l) exported symbols"
|
||||
done
|
||||
if: matrix.name == 'Windows'
|
||||
- name: List Install Tree
|
||||
run: |
|
||||
# omit contents of Init/, ...
|
||||
tree --du -h lean-*-* | grep -E ' (Init|Lean|Lake|LICENSE|[a-z])'
|
||||
- name: Pack
|
||||
run: |
|
||||
dir=$(echo lean-*-*)
|
||||
mkdir pack
|
||||
# high-compression tar.zst + zip for release, fast tar.zst otherwise
|
||||
if [[ '${{ startsWith(github.ref, 'refs/tags/') && matrix.release }}' == true || -n '${{ inputs.nightly }}' || -n '${{ inputs.RELEASE_TAG }}' ]]; then
|
||||
${{ matrix.tar || 'tar' }} cf - $dir | zstd -T0 --no-progress -19 -o pack/$dir.tar.zst
|
||||
zip -rq pack/$dir.zip $dir
|
||||
else
|
||||
${{ matrix.tar || 'tar' }} cf - $dir | zstd -T0 --no-progress -o pack/$dir.tar.zst
|
||||
fi
|
||||
- uses: actions/upload-artifact@v5
|
||||
if: matrix.release
|
||||
with:
|
||||
name: build-${{ matrix.name }}
|
||||
path: pack/*
|
||||
- name: Lean stats
|
||||
run: |
|
||||
build/$TARGET_STAGE/bin/lean --stats src/Lean.lean
|
||||
if: ${{ !matrix.cross }}
|
||||
- name: Test
|
||||
id: test
|
||||
run: |
|
||||
ulimit -c unlimited # coredumps
|
||||
time ctest --preset ${{ matrix.CMAKE_PRESET || 'release' }} --test-dir build/$TARGET_STAGE -j$NPROC --output-junit test-results.xml ${{ matrix.CTEST_OPTIONS }}
|
||||
if: matrix.test
|
||||
- name: Test Summary
|
||||
uses: test-summary/action@v2
|
||||
with:
|
||||
paths: build/${{ env.TARGET_STAGE }}/test-results.xml
|
||||
# prefix `if` above with `always` so it's run even if tests failed
|
||||
if: always() && steps.test.conclusion != 'skipped'
|
||||
- name: Check Test Binary
|
||||
run: ${{ matrix.binary-check }} tests/compile/534.lean.out
|
||||
if: (!matrix.cross) && steps.test.conclusion != 'skipped'
|
||||
- name: Build Stage 2
|
||||
run: |
|
||||
make -C build -j$NPROC stage2
|
||||
if: matrix.test-bench
|
||||
- name: Check Stage 3
|
||||
run: |
|
||||
make -C build -j$NPROC check-stage3
|
||||
if: matrix.check-stage3
|
||||
- name: Test Benchmarks
|
||||
run: |
|
||||
cd tests
|
||||
nix develop -c make -C ../build -j$NPROC bench
|
||||
if: matrix.test-bench
|
||||
- name: Check rebootstrap
|
||||
run: |
|
||||
set -e
|
||||
# clean rebuild in case of Makefile changes/Lake does not detect uncommited stage 0
|
||||
# changes yet
|
||||
make -C build update-stage0
|
||||
make -C build/stage1 clean-stdlib
|
||||
time make -C build -j$NPROC
|
||||
time ctest --preset ${{ matrix.CMAKE_PRESET || 'release' }} --test-dir build/stage1 -j$NPROC
|
||||
if: matrix.check-rebootstrap
|
||||
- name: CCache stats
|
||||
if: always()
|
||||
run: ccache -s
|
||||
- name: Show stacktrace for coredumps
|
||||
if: failure() && runner.os == 'Linux'
|
||||
run: |
|
||||
for c in $(find . -name core); do
|
||||
progbin="$(file $c | sed "s/.*execfn: '\([^']*\)'.*/\1/")"
|
||||
echo bt | $GDB/bin/gdb -q $progbin $c || true
|
||||
done
|
||||
29
.github/workflows/check-prelude.yml
vendored
29
.github/workflows/check-prelude.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: Check for modules that should use `prelude`
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
check-prelude:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# the default is to use a virtual merge commit between the PR and master: just use the PR
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
sparse-checkout: |
|
||||
src/Lean
|
||||
src/Std
|
||||
src/lake/Lake
|
||||
- name: Check Prelude
|
||||
run: |
|
||||
failed_files=""
|
||||
while IFS= read -r -d '' file; do
|
||||
if ! grep -q "^prelude$" "$file"; then
|
||||
failed_files="$failed_files$file\n"
|
||||
fi
|
||||
done < <(find src/Lean src/Std src/lake/Lake -name '*.lean' -print0)
|
||||
if [ -n "$failed_files" ]; then
|
||||
echo -e "The following files should use 'prelude':\n$failed_files"
|
||||
exit 1
|
||||
fi
|
||||
55
.github/workflows/check-stage0.yml
vendored
55
.github/workflows/check-stage0.yml
vendored
@@ -1,55 +0,0 @@
|
||||
name: Check for stage0 changes
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
check-stage0-on-queue:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 0
|
||||
filter: tree:0
|
||||
|
||||
- name: Find base commit
|
||||
if: github.event_name == 'pull_request'
|
||||
run: echo "BASE=$(git merge-base origin/${{ github.base_ref }} HEAD)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Identify stage0 changes
|
||||
run: |
|
||||
git diff "${BASE:-HEAD^}..HEAD" --name-only -- stage0/stdlib > "$RUNNER_TEMP/stage0" || true
|
||||
if test -s "$RUNNER_TEMP/stage0"
|
||||
then
|
||||
echo "CHANGES=yes" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "CHANGES=no" >> "$GITHUB_ENV"
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- if: github.event_name == 'pull_request'
|
||||
name: Set label
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo, number: issue_number } = context.issue;
|
||||
if (process.env.CHANGES == 'yes') {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['changes-stage0'] }).catch(() => {});
|
||||
} else {
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'changes-stage0' }).catch(() => {});
|
||||
}
|
||||
|
||||
- if: env.CHANGES == 'yes'
|
||||
name: Report changes
|
||||
run: |
|
||||
echo "Found changes to stage0/, please do not merge using the merge queue." | tee "$GITHUB_STEP_SUMMARY"
|
||||
# shellcheck disable=SC2129
|
||||
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
||||
cat "$RUNNER_TEMP/stage0" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- if: github.event_name == 'merge_group' && env.CHANGES == 'yes'
|
||||
name: Fail when on the merge queue
|
||||
run: exit 1
|
||||
60
.github/workflows/check-stdlib-flags.yml
vendored
60
.github/workflows/check-stdlib-flags.yml
vendored
@@ -1,60 +0,0 @@
|
||||
name: Check stdlib_flags.h modifications
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, labeled, unlabeled]
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
check-stdlib-flags:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if stdlib_flags.h was modified
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
// Get the list of files changed in this PR
|
||||
const files = await github.paginate(
|
||||
github.rest.pulls.listFiles,
|
||||
{
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number,
|
||||
}
|
||||
);
|
||||
|
||||
// Check if stdlib_flags.h was modified
|
||||
const stdlibFlagsModified = files.some(file =>
|
||||
file.filename === 'src/stdlib_flags.h'
|
||||
);
|
||||
|
||||
if (stdlibFlagsModified) {
|
||||
console.log('src/stdlib_flags.h was modified in this PR');
|
||||
|
||||
// Check if the unlock label is present
|
||||
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.issue.number,
|
||||
});
|
||||
|
||||
const hasUnlockLabel = pr.labels.some(label =>
|
||||
label.name === 'unlock-upstream-stdlib-flags'
|
||||
);
|
||||
|
||||
if (!hasUnlockLabel) {
|
||||
core.setFailed(
|
||||
'src/stdlib_flags.h was modified. This is likely a mistake. If you would like to change ' +
|
||||
'bootstrapping settings or request a stage0 update, you should modify stage0/src/stdlib_flags.h. ' +
|
||||
'If you really want to change src/stdlib_flags.h (which should be extremely rare), set the ' +
|
||||
'unlock-upstream-stdlib-flags label.'
|
||||
);
|
||||
} else {
|
||||
console.log('Found unlock-upstream-stdlib-flags');
|
||||
}
|
||||
} else {
|
||||
console.log('src/stdlib_flags.h was not modified');
|
||||
}
|
||||
760
.github/workflows/ci.yml
vendored
760
.github/workflows/ci.yml
vendored
@@ -6,20 +6,10 @@ on:
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
merge_group:
|
||||
schedule:
|
||||
- cron: '0 7 * * *' # 8AM CET/11PM PT
|
||||
# for manual re-release of a nightly
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: 'Action'
|
||||
required: true
|
||||
default: 'release nightly'
|
||||
type: choice
|
||||
options:
|
||||
- release nightly
|
||||
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
|
||||
@@ -31,10 +21,10 @@ jobs:
|
||||
configure:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
# Should we run only a quick CI? Yes on a pull request without the full-ci label
|
||||
quick: ${{ steps.set-quick.outputs.quick }}
|
||||
# The build matrix, dynamically generated here
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
# secondary build jobs that should not block the CI success/merge queue
|
||||
matrix-secondary: ${{ steps.set-matrix.outputs.matrix-secondary }}
|
||||
matrix: ${{ steps.set-matrix.outputs.result }}
|
||||
# Should we make a nightly release? If so, this output contains the lean version string, else it is empty
|
||||
nightly: ${{ steps.set-nightly.outputs.nightly }}
|
||||
# Should this be the CI for a tagged release?
|
||||
@@ -49,34 +39,172 @@ jobs:
|
||||
RELEASE_TAG: ${{ steps.set-release.outputs.RELEASE_TAG }}
|
||||
|
||||
steps:
|
||||
- name: Run quick CI?
|
||||
id: set-quick
|
||||
env:
|
||||
quick: ${{
|
||||
github.event_name == 'pull_request' && !contains( github.event.pull_request.labels.*.name, 'full-ci')
|
||||
}}
|
||||
run: |
|
||||
echo "quick=${{env.quick}}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Configure build matrix
|
||||
id: set-matrix
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const quick = ${{ steps.set-quick.outputs.quick }};
|
||||
console.log(`quick: ${quick}`)
|
||||
let matrix = [
|
||||
{
|
||||
// portable release build: use channel with older glibc (2.27)
|
||||
"name": "Linux LLVM",
|
||||
"os": "ubuntu-latest",
|
||||
"release": false,
|
||||
"quick": false,
|
||||
"shell": "nix-shell --arg pkgsDist \"import (fetchTarball \\\"channel:nixos-19.03\\\") {{}}\" --run \"bash -euxo pipefail {0}\"",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
|
||||
"binary-check": "ldd -v",
|
||||
// foreign code may be linked against more recent glibc
|
||||
// reverse-ffi needs to be updated to link to LLVM libraries
|
||||
"CTEST_OPTIONS": "-E 'foreign|leanlaketest_reverse-ffi'",
|
||||
"CMAKE_OPTIONS": "-DLLVM=ON -DLLVM_CONFIG=${GITHUB_WORKSPACE}/build/llvm-host/bin/llvm-config"
|
||||
},
|
||||
{
|
||||
"name": "Linux release",
|
||||
"os": "ubuntu-latest",
|
||||
"release": true,
|
||||
"quick": true,
|
||||
"shell": "nix-shell --arg pkgsDist \"import (fetchTarball \\\"channel:nixos-19.03\\\") {{}}\" --run \"bash -euxo pipefail {0}\"",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
|
||||
"binary-check": "ldd -v",
|
||||
// foreign code may be linked against more recent glibc
|
||||
"CTEST_OPTIONS": "-E 'foreign'"
|
||||
},
|
||||
{
|
||||
"name": "Linux",
|
||||
"os": "ubuntu-latest",
|
||||
"check-stage3": true,
|
||||
"test-speedcenter": true,
|
||||
"quick": false,
|
||||
},
|
||||
{
|
||||
"name": "Linux Debug",
|
||||
"os": "ubuntu-latest",
|
||||
"quick": false,
|
||||
"CMAKE_OPTIONS": "-DCMAKE_BUILD_TYPE=Debug",
|
||||
// exclude seriously slow tests
|
||||
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest'"
|
||||
},
|
||||
{
|
||||
"name": "Linux fsanitize",
|
||||
"os": "ubuntu-latest",
|
||||
"quick": false,
|
||||
// turn off custom allocator & symbolic functions to make LSAN do its magic
|
||||
"CMAKE_OPTIONS": "-DLEAN_EXTRA_CXX_FLAGS=-fsanitize=address,undefined -DLEANC_EXTRA_FLAGS='-fsanitize=address,undefined -fsanitize-link-c++-runtime' -DSMALL_ALLOCATOR=OFF -DBSYMBOLIC=OFF",
|
||||
// exclude seriously slow/problematic tests (laketests crash)
|
||||
"CTEST_OPTIONS": "-E 'interactivetest|leanpkgtest|laketest|benchtest'"
|
||||
},
|
||||
{
|
||||
"name": "macOS",
|
||||
"os": "macos-latest",
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-apple-darwin.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm*",
|
||||
"binary-check": "otool -L",
|
||||
"tar": "gtar" // https://github.com/actions/runner-images/issues/2619
|
||||
},
|
||||
{
|
||||
"name": "macOS aarch64",
|
||||
"os": "macos-latest",
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"cross": true,
|
||||
"cross_target": "aarch64-apple-darwin",
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
"CMAKE_OPTIONS": "-DUSE_GMP=OFF -DLEAN_INSTALL_SUFFIX=-darwin_aarch64",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-apple-darwin.tar.zst https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-apple-darwin.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm-aarch64-* lean-llvm-x86_64-*",
|
||||
"binary-check": "otool -L",
|
||||
"tar": "gtar" // https://github.com/actions/runner-images/issues/2619
|
||||
},
|
||||
{
|
||||
"name": "Windows",
|
||||
"os": "windows-2022",
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"shell": "msys2 {0}",
|
||||
"CMAKE_OPTIONS": "-G \"Unix Makefiles\" -DUSE_GMP=OFF",
|
||||
// for reasons unknown, interactivetests are flaky on Windows
|
||||
"CTEST_OPTIONS": "--repeat until-pass:2",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-w64-windows-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-mingw.sh lean-llvm*",
|
||||
"binary-check": "ldd"
|
||||
},
|
||||
{
|
||||
"name": "Linux aarch64",
|
||||
"os": "ubuntu-latest",
|
||||
"CMAKE_OPTIONS": "-DUSE_GMP=OFF -DLEAN_INSTALL_SUFFIX=-linux_aarch64",
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"cross": true,
|
||||
"cross_target": "aarch64-unknown-linux-gnu",
|
||||
"shell": "nix-shell --arg pkgsDist \"import (fetchTarball \\\"channel:nixos-19.03\\\") {{ localSystem.config = \\\"aarch64-unknown-linux-gnu\\\"; }}\" --run \"bash -euxo pipefail {0}\"",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-x86_64-linux-gnu.tar.zst https://github.com/leanprover/lean-llvm/releases/download/15.0.1/lean-llvm-aarch64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm-aarch64-* lean-llvm-x86_64-*"
|
||||
},
|
||||
{
|
||||
"name": "Linux 32bit",
|
||||
"os": "ubuntu-latest",
|
||||
// Use 32bit on stage0 and stage1 to keep oleans compatible
|
||||
"CMAKE_OPTIONS": "-DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_MMAP=OFF -DUSE_GMP=OFF -DLEAN_EXTRA_CXX_FLAGS='-m32' -DLEANC_OPTS='-m32' -DMMAP=OFF -DLEAN_INSTALL_SUFFIX=-linux_x86",
|
||||
"cmultilib": true,
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"cross": true,
|
||||
"shell": "bash -euxo pipefail {0}"
|
||||
},
|
||||
{
|
||||
"name": "Web Assembly",
|
||||
"os": "ubuntu-latest",
|
||||
// Build a native 32bit binary in stage0 and use it to compile the oleans and the wasm build
|
||||
"CMAKE_OPTIONS": "-DCMAKE_C_COMPILER_WORKS=1 -DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_CMAKE_CXX_COMPILER=clang++ -DSTAGE0_CMAKE_C_COMPILER=clang -DSTAGE0_CMAKE_EXECUTABLE_SUFFIX=\"\" -DUSE_GMP=OFF -DMMAP=OFF -DSTAGE0_MMAP=OFF -DCMAKE_AR=../emsdk/emsdk-main/upstream/emscripten/emar -DCMAKE_TOOLCHAIN_FILE=../emsdk/emsdk-main/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DLEAN_INSTALL_SUFFIX=-linux_wasm32",
|
||||
"wasm": true,
|
||||
"cmultilib": true,
|
||||
"release": true,
|
||||
"quick": false,
|
||||
"cross": true,
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
// Just a few selected tests because wasm is slow
|
||||
"CTEST_OPTIONS": "-R \"leantest_1007\\.lean|leantest_Format\\.lean|leanruntest\\_1037.lean|leanruntest_ac_rfl\\.lean\""
|
||||
}
|
||||
];
|
||||
console.log(`matrix:\n${JSON.stringify(matrix, null, 2)}`)
|
||||
if (quick) {
|
||||
return matrix.filter((job) => job.quick)
|
||||
} else {
|
||||
return matrix
|
||||
}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v3
|
||||
# don't schedule nightlies on forks
|
||||
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4' || inputs.action == 'release nightly' || (startsWith(github.ref, 'refs/tags/') && github.repository == 'leanprover/lean4')
|
||||
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4'
|
||||
- name: Set Nightly
|
||||
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4' || inputs.action == 'release nightly'
|
||||
if: github.event_name == 'schedule' && github.repository == 'leanprover/lean4'
|
||||
id: set-nightly
|
||||
run: |
|
||||
if [[ -n '${{ secrets.PUSH_NIGHTLY_TOKEN }}' ]]; then
|
||||
git remote add nightly https://foo:'${{ secrets.PUSH_NIGHTLY_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-nightly.git
|
||||
git fetch nightly --tags
|
||||
if [[ '${{ github.event_name }}' == 'workflow_dispatch' ]]; then
|
||||
# Manual re-release: create a revision of the most recent nightly
|
||||
BASE_NIGHTLY=$(git tag -l 'nightly-*' | sort -rV | head -1)
|
||||
# Strip any existing -revK suffix to get the base date tag
|
||||
BASE_NIGHTLY="${BASE_NIGHTLY%%-rev*}"
|
||||
REV=1
|
||||
while git rev-parse "refs/tags/${BASE_NIGHTLY}-rev${REV}" >/dev/null 2>&1; do
|
||||
REV=$((REV + 1))
|
||||
done
|
||||
LEAN_VERSION_STRING="${BASE_NIGHTLY}-rev${REV}"
|
||||
LEAN_VERSION_STRING="nightly-$(date -u +%F)"
|
||||
# do nothing if commit already has a different tag
|
||||
if [[ "$(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || echo "$LEAN_VERSION_STRING")" == "$LEAN_VERSION_STRING" ]]; then
|
||||
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# Scheduled: do nothing if commit already has a different tag
|
||||
LEAN_VERSION_STRING="nightly-$(date -u +%F)"
|
||||
if [[ "$(git name-rev --name-only --tags --no-undefined HEAD 2> /dev/null || echo "$LEAN_VERSION_STRING")" == "$LEAN_VERSION_STRING" ]]; then
|
||||
echo "nightly=$LEAN_VERSION_STRING" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -112,371 +240,235 @@ jobs:
|
||||
echo "Tag ${TAG_NAME} did not match SemVer regex."
|
||||
fi
|
||||
|
||||
- name: Check for custom releases (e.g., not in the main lean repository)
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository != 'leanprover/lean4'
|
||||
id: set-release-custom
|
||||
run: |
|
||||
TAG_NAME="${GITHUB_REF##*/}"
|
||||
echo "RELEASE_TAG=$TAG_NAME" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate CMakeLists.txt version matches tag
|
||||
if: steps.set-release.outputs.RELEASE_TAG != ''
|
||||
run: |
|
||||
echo "Validating CMakeLists.txt version matches tag ${{ steps.set-release.outputs.RELEASE_TAG }}"
|
||||
|
||||
# Extract version values from CMakeLists.txt
|
||||
CMAKE_MAJOR=$(grep -E "^set\(LEAN_VERSION_MAJOR " src/CMakeLists.txt | grep -oE '[0-9]+')
|
||||
CMAKE_MINOR=$(grep -E "^set\(LEAN_VERSION_MINOR " src/CMakeLists.txt | grep -oE '[0-9]+')
|
||||
CMAKE_PATCH=$(grep -E "^set\(LEAN_VERSION_PATCH " src/CMakeLists.txt | grep -oE '[0-9]+')
|
||||
CMAKE_IS_RELEASE=$(grep -m 1 -E "^set\(LEAN_VERSION_IS_RELEASE " src/CMakeLists.txt | 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 }}"
|
||||
TAG_MINOR="${{ steps.set-release.outputs.LEAN_VERSION_MINOR }}"
|
||||
TAG_PATCH="${{ steps.set-release.outputs.LEAN_VERSION_PATCH }}"
|
||||
|
||||
ERRORS=""
|
||||
|
||||
if [[ "$CMAKE_MAJOR" != "$TAG_MAJOR" ]]; then
|
||||
ERRORS+="LEAN_VERSION_MAJOR: expected $TAG_MAJOR, found $CMAKE_MAJOR\n"
|
||||
fi
|
||||
if [[ "$CMAKE_MINOR" != "$TAG_MINOR" ]]; then
|
||||
ERRORS+="LEAN_VERSION_MINOR: expected $TAG_MINOR, found $CMAKE_MINOR\n"
|
||||
fi
|
||||
if [[ "$CMAKE_PATCH" != "$TAG_PATCH" ]]; then
|
||||
ERRORS+="LEAN_VERSION_PATCH: expected $TAG_PATCH, found $CMAKE_PATCH\n"
|
||||
fi
|
||||
if [[ "$CMAKE_IS_RELEASE" != "1" ]]; then
|
||||
ERRORS+="LEAN_VERSION_IS_RELEASE: expected 1, found $CMAKE_IS_RELEASE\n"
|
||||
fi
|
||||
|
||||
if [[ -n "$ERRORS" ]]; then
|
||||
echo "::error::Version mismatch between tag and src/CMakeLists.txt"
|
||||
echo ""
|
||||
echo "Tag ${{ steps.set-release.outputs.RELEASE_TAG }} expects version $TAG_MAJOR.$TAG_MINOR.$TAG_PATCH"
|
||||
echo "But src/CMakeLists.txt has mismatched values:"
|
||||
echo -e "$ERRORS"
|
||||
echo ""
|
||||
echo "Fix src/CMakeLists.txt, delete the tag, and re-tag."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Version validation passed: $TAG_MAJOR.$TAG_MINOR.$TAG_PATCH"
|
||||
|
||||
# 0: PRs without special label
|
||||
# 1: PRs with `merge-ci` label, merge queue checks, master commits
|
||||
# 2: nightlies
|
||||
# 3: PRs with `release-ci` or `lake-ci` label, full releases
|
||||
- name: Set check level
|
||||
id: set-level
|
||||
# We do not use github.event.pull_request.labels.*.name here because
|
||||
# re-running a run does not update that list, and we do want to be able to
|
||||
# rerun the workflow run after setting the `release-ci`/`merge-ci` labels.
|
||||
run: |
|
||||
check_level=0
|
||||
fast=false
|
||||
lake_ci=false
|
||||
|
||||
if [[ -n "${{ steps.set-release.outputs.RELEASE_TAG }}" || -n "${{ steps.set-release-custom.outputs.RELEASE_TAG }}" ]]; then
|
||||
check_level=3
|
||||
elif [[ -n "${{ steps.set-nightly.outputs.nightly }}" ]]; then
|
||||
check_level=2
|
||||
elif [[ "${{ github.event_name }}" != "pull_request" ]]; then
|
||||
check_level=1
|
||||
else
|
||||
labels="$(gh api repos/${{ github.repository_owner }}/${{ github.event.repository.name }}/pulls/${{ github.event.pull_request.number }} --jq '.labels')"
|
||||
if echo "$labels" | grep -q "release-ci"; then
|
||||
check_level=3
|
||||
elif echo "$labels" | grep -q "merge-ci"; then
|
||||
check_level=1
|
||||
fi
|
||||
if echo "$labels" | grep -q "lake-ci"; then
|
||||
lake_ci=true
|
||||
fi
|
||||
if echo "$labels" | grep -q "fast-ci"; then
|
||||
fast=true
|
||||
fi
|
||||
fi
|
||||
|
||||
{
|
||||
echo "check-level=$check_level"
|
||||
echo "fast=$fast"
|
||||
echo "lake-ci=$lake_ci"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Configure build matrix
|
||||
id: set-matrix
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const level = ${{ steps.set-level.outputs.check-level }};
|
||||
const fast = ${{ steps.set-level.outputs.fast }};
|
||||
const lakeCi = "${{ steps.set-level.outputs.lake-ci }}" == "true";
|
||||
console.log(`level: ${level}, fast: ${fast}`);
|
||||
// use large runners where available (original repo)
|
||||
let large = ${{ github.repository == 'leanprover/lean4' }};
|
||||
const isPr = "${{ github.event_name }}" == "pull_request";
|
||||
const isPushToMaster = "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == "master";
|
||||
let matrix = [
|
||||
/* TODO: to be updated to new LLVM
|
||||
{
|
||||
"name": "Linux LLVM",
|
||||
"os": "ubuntu-latest",
|
||||
"release": false,
|
||||
"enabled": level >= 2,
|
||||
"test": true,
|
||||
"shell": "nix develop .#oldGlibc -c bash -euxo pipefail {0}",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
|
||||
"binary-check": "ldd -v",
|
||||
// foreign code may be linked against more recent glibc
|
||||
// reverse-ffi needs to be updated to link to LLVM libraries
|
||||
"CTEST_OPTIONS": "-E 'foreign|leanlaketest_reverse-ffi'",
|
||||
"CMAKE_OPTIONS": "-DLLVM=ON -DLLVM_CONFIG=${GITHUB_WORKSPACE}/build/llvm-host/bin/llvm-config"
|
||||
}, */
|
||||
{
|
||||
// portable release build: use channel with older glibc (2.26)
|
||||
"name": "Linux release",
|
||||
// usually not a bottleneck so make exclusive to `fast-ci`
|
||||
"os": large && fast ? "nscloud-ubuntu-22.04-amd64-8x16-with-cache" : "ubuntu-latest",
|
||||
"release": true,
|
||||
// Special handling for release jobs. We want:
|
||||
// 1. To run it in PRs so developers get PR toolchains (so secondary without tests is sufficient)
|
||||
// 2. To skip it in merge queues as it takes longer than the
|
||||
// Linux lake build and adds little value in the merge queue
|
||||
// 3. To run it in release (obviously)
|
||||
// 4. To run it for pushes to master so that pushes to master have a Linux toolchain
|
||||
// available as an artifact for Grove to use.
|
||||
"enabled": isPr || level != 1 || isPushToMaster,
|
||||
"test": level >= 1,
|
||||
"secondary": level == 0,
|
||||
"shell": "nix develop .#oldGlibc -c bash -euxo pipefail {0}",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
|
||||
"binary-check": "ldd -v",
|
||||
// foreign code may be linked against more recent glibc
|
||||
"CTEST_OPTIONS": "-E 'foreign'",
|
||||
},
|
||||
{
|
||||
"name": "Linux Lake",
|
||||
"os": large ? "nscloud-ubuntu-22.04-amd64-8x16-with-cache" : "ubuntu-latest",
|
||||
"enabled": true,
|
||||
"check-rebootstrap": level >= 1,
|
||||
"check-stage3": level >= 2,
|
||||
"test": true,
|
||||
// NOTE: `test-bench` currently seems to be broken on `ubuntu-latest`
|
||||
"test-bench": large && level >= 2,
|
||||
// We are not warning-free yet on all platforms, start here
|
||||
"CMAKE_OPTIONS": "-DLEAN_EXTRA_CXX_FLAGS=-Werror",
|
||||
},
|
||||
{
|
||||
"name": "Linux Reldebug",
|
||||
"os": "ubuntu-latest",
|
||||
"enabled": level >= 2,
|
||||
"test": true,
|
||||
"CMAKE_PRESET": "reldebug",
|
||||
// * `elab_bench/big_do` crashes with exit code 134
|
||||
"CTEST_OPTIONS": "-E 'elab_bench/big_do'",
|
||||
},
|
||||
{
|
||||
"name": "Linux fsanitize",
|
||||
// Always run on large if available, more reliable regarding timeouts
|
||||
"os": large ? "nscloud-ubuntu-22.04-amd64-16x32-with-cache" : "ubuntu-latest",
|
||||
"enabled": level >= 2,
|
||||
// do not fail nightlies on this for now
|
||||
"secondary": level <= 2,
|
||||
"test": true,
|
||||
// turn off custom allocator & symbolic functions to make LSAN do its magic
|
||||
"CMAKE_PRESET": "sanitize",
|
||||
// * `StackOverflow*` correctly triggers ubsan.
|
||||
// * `reverse-ffi` fails to link in sanitizers.
|
||||
// * `interactive` and `async_select_channel` fail nondeterministically, would need
|
||||
// to be investigated..
|
||||
// * 9366 is too close to timeout.
|
||||
// * `bv_` sometimes times out calling into cadical even though we should be using
|
||||
// the standard compile flags for it.
|
||||
// * `grind_guide` always times out.
|
||||
// * `pkg/|lake/` tests sometimes time out (likely even hang), related to Lake CI
|
||||
// failures?
|
||||
"CTEST_OPTIONS": "-E 'StackOverflow|reverse-ffi|interactive|async_select_channel|9366|run/bv_|grind_guide|grind_bitvec2|grind_constProp|grind_indexmap|grind_list|grind_lint|grind_array_attach|grind_ite_trace|pkg/|lake/'"
|
||||
},
|
||||
{
|
||||
"name": "macOS",
|
||||
"os": "macos-15-intel",
|
||||
"release": true,
|
||||
"test": false, // Tier 2 platform
|
||||
"enabled": level >= 2,
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-apple-darwin.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm*",
|
||||
"binary-check": "otool -L",
|
||||
"tar": "gtar", // https://github.com/actions/runner-images/issues/2619
|
||||
"CTEST_OPTIONS": "-E 'leanlaketest_hello'", // started failing from unpack
|
||||
},
|
||||
{
|
||||
"name": "macOS aarch64",
|
||||
// standard GH runner only comes with 7GB so use large runner if possible when running tests
|
||||
"os": large && (fast || level >= 1) ? "nscloud-macos-sequoia-arm64-6x14" : "macos-15",
|
||||
"CMAKE_OPTIONS": "-DLEAN_INSTALL_SUFFIX=-darwin_aarch64",
|
||||
"release": true,
|
||||
"shell": "bash -euxo pipefail {0}",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-aarch64-apple-darwin.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-macos.sh lean-llvm*",
|
||||
"binary-check": "otool -L",
|
||||
"tar": "gtar", // https://github.com/actions/runner-images/issues/2619
|
||||
// See "Linux release" for release job levels; Grove is not a concern here
|
||||
"enabled": isPr || level != 1,
|
||||
"test": level >= 1,
|
||||
"secondary": level == 0,
|
||||
},
|
||||
{
|
||||
"name": "Windows",
|
||||
"os": large && (fast || level >= 2) ? "namespace-profile-windows-amd64-4x16" : "windows-2022",
|
||||
"release": true,
|
||||
"enabled": level >= 2,
|
||||
"test": true,
|
||||
"shell": "msys2 {0}",
|
||||
"CMAKE_OPTIONS": "-G \"Unix Makefiles\"",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-x86_64-w64-windows-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-mingw.sh lean-llvm*",
|
||||
"binary-check": "ldd",
|
||||
},
|
||||
{
|
||||
"name": "Linux aarch64",
|
||||
"os": "nscloud-ubuntu-22.04-arm64-4x16",
|
||||
"CMAKE_OPTIONS": "-DLEAN_INSTALL_SUFFIX=-linux_aarch64",
|
||||
"release": true,
|
||||
"enabled": level >= 2,
|
||||
"test": true,
|
||||
"shell": "nix develop .#oldGlibcAArch -c bash -euxo pipefail {0}",
|
||||
"llvm-url": "https://github.com/leanprover/lean-llvm/releases/download/19.1.2/lean-llvm-aarch64-linux-gnu.tar.zst",
|
||||
"prepare-llvm": "../script/prepare-llvm-linux.sh lean-llvm*",
|
||||
},
|
||||
// Started running out of memory building expensive modules, a 2GB heap is just not that much even before fragmentation
|
||||
//{
|
||||
// "name": "Linux 32bit",
|
||||
// "os": "ubuntu-latest",
|
||||
// // Use 32bit on stage0 and stage1 to keep oleans compatible
|
||||
// "CMAKE_OPTIONS": "-DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_MMAP=OFF -DUSE_GMP=OFF -DLEAN_EXTRA_CXX_FLAGS='-m32' -DLEANC_OPTS='-m32' -DMMAP=OFF -DLEAN_INSTALL_SUFFIX=-linux_x86 -DCMAKE_LIBRARY_PATH=/usr/lib/i386-linux-gnu/ -DSTAGE0_CMAKE_LIBRARY_PATH=/usr/lib/i386-linux-gnu/ -DPKG_CONFIG_EXECUTABLE=/usr/bin/i386-linux-gnu-pkg-config",
|
||||
// "cmultilib": true,
|
||||
// "release": true,
|
||||
// "enabled": level >= 2,
|
||||
// "cross": true,
|
||||
// "shell": "bash -euxo pipefail {0}"
|
||||
//}
|
||||
// {
|
||||
// "name": "Web Assembly",
|
||||
// "os": "ubuntu-latest",
|
||||
// // Build a native 32bit binary in stage0 and use it to compile the oleans and the wasm build
|
||||
// "CMAKE_OPTIONS": "-DCMAKE_C_COMPILER_WORKS=1 -DSTAGE0_USE_GMP=OFF -DSTAGE0_LEAN_EXTRA_CXX_FLAGS='-m32' -DSTAGE0_LEANC_OPTS='-m32' -DSTAGE0_CMAKE_CXX_COMPILER=clang++ -DSTAGE0_CMAKE_C_COMPILER=clang -DSTAGE0_CMAKE_EXECUTABLE_SUFFIX=\"\" -DUSE_GMP=OFF -DMMAP=OFF -DSTAGE0_MMAP=OFF -DCMAKE_AR=../emsdk/emsdk-main/upstream/emscripten/emar -DCMAKE_TOOLCHAIN_FILE=../emsdk/emsdk-main/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DLEAN_INSTALL_SUFFIX=-linux_wasm32 -DSTAGE0_CMAKE_LIBRARY_PATH=/usr/lib/i386-linux-gnu/",
|
||||
// "wasm": true,
|
||||
// "cmultilib": true,
|
||||
// "release": true,
|
||||
// "enabled": level >= 2,
|
||||
// "cross": true,
|
||||
// "shell": "bash -euxo pipefail {0}",
|
||||
// // Just a few selected tests because wasm is slow
|
||||
// "CTEST_OPTIONS": "-R \"leantest_1007\\.lean|leantest_Format\\.lean|leanruntest\\_1037.lean|leanruntest_ac_rfl\\.lean|leanruntest_tempfile.lean\\.|leanruntest_libuv\\.lean\""
|
||||
// }
|
||||
];
|
||||
for (const job of matrix) {
|
||||
if (job["prepare-llvm"]) {
|
||||
// `USE_LAKE` is not compatible with `prepare-llvm` currently
|
||||
job["CMAKE_OPTIONS"] = (job["CMAKE_OPTIONS"] ? job["CMAKE_OPTIONS"] + " " : "") + "-DUSE_LAKE=OFF";
|
||||
}
|
||||
}
|
||||
if (lakeCi) {
|
||||
for (const job of matrix) {
|
||||
job["CMAKE_OPTIONS"] = (job["CMAKE_OPTIONS"] ? job["CMAKE_OPTIONS"] + " " : "") + "-DLAKE_CI=ON";
|
||||
}
|
||||
}
|
||||
console.log(`matrix:\n${JSON.stringify(matrix, null, 2)}`);
|
||||
matrix = matrix.filter((job) => job["enabled"]);
|
||||
core.setOutput('matrix', matrix.filter((job) => !job["secondary"]));
|
||||
core.setOutput('matrix-secondary', matrix.filter((job) => job["secondary"]));
|
||||
|
||||
build:
|
||||
needs: [configure]
|
||||
if: github.event_name != 'schedule' || github.repository == 'leanprover/lean4'
|
||||
needs: [configure]
|
||||
uses: ./.github/workflows/build-template.yml
|
||||
with:
|
||||
config: ${{needs.configure.outputs.matrix}}
|
||||
nightly: ${{ needs.configure.outputs.nightly }}
|
||||
LEAN_VERSION_MAJOR: ${{ needs.configure.outputs.LEAN_VERSION_MAJOR }}
|
||||
LEAN_VERSION_MINOR: ${{ needs.configure.outputs.LEAN_VERSION_MINOR }}
|
||||
LEAN_VERSION_PATCH: ${{ needs.configure.outputs.LEAN_VERSION_PATCH }}
|
||||
LEAN_SPECIAL_VERSION_DESC: ${{ needs.configure.outputs.LEAN_SPECIAL_VERSION_DESC }}
|
||||
RELEASE_TAG: ${{ needs.configure.outputs.RELEASE_TAG }}
|
||||
secrets: inherit
|
||||
|
||||
# build jobs that should not be considered by `all-done` below
|
||||
build-secondary:
|
||||
needs: [configure]
|
||||
if: needs.configure.outputs.matrix-secondary != '[]'
|
||||
uses: ./.github/workflows/build-template.yml
|
||||
with:
|
||||
config: ${{needs.configure.outputs.matrix-secondary}}
|
||||
nightly: ${{ needs.configure.outputs.nightly }}
|
||||
LEAN_VERSION_MAJOR: ${{ needs.configure.outputs.LEAN_VERSION_MAJOR }}
|
||||
LEAN_VERSION_MINOR: ${{ needs.configure.outputs.LEAN_VERSION_MINOR }}
|
||||
LEAN_VERSION_PATCH: ${{ needs.configure.outputs.LEAN_VERSION_PATCH }}
|
||||
LEAN_SPECIAL_VERSION_DESC: ${{ needs.configure.outputs.LEAN_SPECIAL_VERSION_DESC }}
|
||||
RELEASE_TAG: ${{ needs.configure.outputs.RELEASE_TAG }}
|
||||
secrets: inherit
|
||||
strategy:
|
||||
matrix:
|
||||
include: ${{fromJson(needs.configure.outputs.matrix)}}
|
||||
# complete all jobs
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.shell || 'nix-shell --run "bash -euxo pipefail {0}"' }}
|
||||
name: ${{ matrix.name }}
|
||||
env:
|
||||
# must be inside workspace
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPRESS: true
|
||||
# current cache limit
|
||||
CCACHE_MAXSIZE: 200M
|
||||
# squelch error message about missing nixpkgs channel
|
||||
NIX_BUILD_SHELL: bash
|
||||
LSAN_OPTIONS: max_leaks=10
|
||||
# somehow MinGW clang64 (or cmake?) defaults to `g++` even though it doesn't exist
|
||||
CXX: c++
|
||||
MACOSX_DEPLOYMENT_TARGET: 10.15
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
# the default is to use a virtual merge commit between the PR and master: just use the PR
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v18
|
||||
with:
|
||||
install_url: https://releases.nixos.org/nix/nix-2.12.0/install
|
||||
if: matrix.os == 'ubuntu-latest' && !matrix.cmultilib
|
||||
- name: Install MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: clang64
|
||||
# `:p` means prefix with appropriate msystem prefix
|
||||
pacboy: "make python cmake:p clang:p ccache:p gmp:p git zip unzip diffutils binutils tree zstd:p tar"
|
||||
if: matrix.os == 'windows-2022'
|
||||
- name: Install Brew Packages
|
||||
run: |
|
||||
brew install ccache tree zstd coreutils gmp
|
||||
if: matrix.os == 'macos-latest'
|
||||
- name: Setup emsdk
|
||||
uses: mymindstorm/setup-emsdk@v12
|
||||
with:
|
||||
version: 3.1.44
|
||||
actions-cache-folder: emsdk
|
||||
if: matrix.wasm
|
||||
- name: Install 32bit c libs
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-multilib g++-multilib ccache
|
||||
if: matrix.cmultilib
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .ccache
|
||||
key: ${{ matrix.name }}-build-v3-${{ github.sha }}
|
||||
# fall back to (latest) previous cache
|
||||
restore-keys: |
|
||||
${{ matrix.name }}-build-v3
|
||||
- name: Setup
|
||||
run: |
|
||||
# open nix-shell once for initial setup
|
||||
true
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Set up core dumps
|
||||
run: |
|
||||
mkdir -p $PWD/coredumps
|
||||
# store in current directory, for easy uploading together with binary
|
||||
echo $PWD/coredumps/%e.%p.%t | sudo tee /proc/sys/kernel/core_pattern
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
- name: Build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
ulimit -c unlimited # coredumps
|
||||
# arguments passed to `cmake`
|
||||
# this also enables githash embedding into stage 1 library
|
||||
OPTIONS=(-DCHECK_OLEAN_VERSION=ON)
|
||||
OPTIONS+=(-DLEAN_EXTRA_MAKE_OPTS=-DwarningAsError=true)
|
||||
if [[ -n '${{ matrix.cross_target }}' ]]; then
|
||||
# used by `prepare-llvm`
|
||||
export EXTRA_FLAGS=--target=${{ matrix.cross_target }}
|
||||
OPTIONS+=(-DLEAN_PLATFORM_TARGET=${{ matrix.cross_target }})
|
||||
fi
|
||||
if [[ -n '${{ matrix.prepare-llvm }}' ]]; then
|
||||
wget -q ${{ matrix.llvm-url }}
|
||||
PREPARE="$(${{ matrix.prepare-llvm }})"
|
||||
eval "OPTIONS+=($PREPARE)"
|
||||
fi
|
||||
if [[ -n '${{ matrix.release }}' && -n '${{ needs.configure.outputs.nightly }}' ]]; then
|
||||
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=${{ needs.configure.outputs.nightly }})
|
||||
fi
|
||||
if [[ -n '${{ matrix.release }}' && -n '${{ needs.configure.outputs.RELEASE_TAG }}' ]]; then
|
||||
OPTIONS+=(-DLEAN_VERSION_MAJOR=${{ needs.configure.outputs.LEAN_VERSION_MAJOR }})
|
||||
OPTIONS+=(-DLEAN_VERSION_MINOR=${{ needs.configure.outputs.LEAN_VERSION_MINOR }})
|
||||
OPTIONS+=(-DLEAN_VERSION_PATCH=${{ needs.configure.outputs.LEAN_VERSION_PATCH }})
|
||||
OPTIONS+=(-DLEAN_VERSION_IS_RELEASE=1)
|
||||
OPTIONS+=(-DLEAN_SPECIAL_VERSION_DESC=${{ needs.configure.outputs.LEAN_SPECIAL_VERSION_DESC }})
|
||||
fi
|
||||
# contortion to support empty OPTIONS with old macOS bash
|
||||
cmake .. ${{ matrix.CMAKE_OPTIONS }} ${OPTIONS[@]+"${OPTIONS[@]}"} -DLEAN_INSTALL_PREFIX=$PWD/..
|
||||
make -j4
|
||||
make install
|
||||
- name: Check Binaries
|
||||
run: ${{ matrix.binary-check }} lean-*/bin/* || true
|
||||
- name: List Install Tree
|
||||
run: |
|
||||
# omit contents of Init/, ...
|
||||
tree --du -h lean-*-* | grep -E ' (Init|Lean|Lake|LICENSE|[a-z])'
|
||||
- name: Pack
|
||||
run: |
|
||||
dir=$(echo lean-*-*)
|
||||
mkdir pack
|
||||
# high-compression tar.zst + zip for release, fast tar.zst otherwise
|
||||
if [[ '${{ startsWith(github.ref, 'refs/tags/') && matrix.release }}' == true || -n '${{ needs.configure.outputs.nightly }}' || -n '${{ needs.configure.outputs.RELEASE_TAG }}' ]]; then
|
||||
${{ matrix.tar || 'tar' }} cf - $dir | zstd -T0 --no-progress -19 -o pack/$dir.tar.zst
|
||||
zip -rq pack/$dir.zip $dir
|
||||
else
|
||||
${{ matrix.tar || 'tar' }} cf - $dir | zstd -T0 --no-progress -o pack/$dir.tar.zst
|
||||
fi
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: matrix.release
|
||||
with:
|
||||
name: build-${{ matrix.name }}
|
||||
path: pack/*
|
||||
- name: Lean stats
|
||||
run: |
|
||||
build/stage1/bin/lean --stats src/Lean.lean
|
||||
if: ${{ !matrix.cross }}
|
||||
- name: Test
|
||||
run: |
|
||||
cd build/stage1
|
||||
ulimit -c unlimited # coredumps
|
||||
# exclude nonreproducible test
|
||||
ctest -j4 --output-on-failure ${{ matrix.CTEST_OPTIONS }} < /dev/null
|
||||
if: (matrix.wasm || !matrix.cross) && needs.configure.outputs.quick == 'false'
|
||||
- name: Check Test Binary
|
||||
run: ${{ matrix.binary-check }} tests/compiler/534.lean.out
|
||||
if: ${{ !matrix.cross && needs.configure.outputs.quick == 'false' }}
|
||||
- name: Build Stage 2
|
||||
run: |
|
||||
cd build
|
||||
ulimit -c unlimited # coredumps
|
||||
make -j4 stage2
|
||||
if: matrix.test-speedcenter
|
||||
- name: Check Stage 3
|
||||
run: |
|
||||
cd build
|
||||
ulimit -c unlimited # coredumps
|
||||
make -j4 check-stage3
|
||||
if: matrix.test-speedcenter
|
||||
- name: Test Speedcenter Benchmarks
|
||||
run: |
|
||||
echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid
|
||||
export BUILD=$PWD/build PATH=$PWD/build/stage1/bin:$PATH
|
||||
cd tests/bench
|
||||
nix shell .#temci -c temci exec --config speedcenter.yaml --included_blocks fast --runs 1
|
||||
if: matrix.test-speedcenter
|
||||
- name: Check rebootstrap
|
||||
run: |
|
||||
cd build
|
||||
ulimit -c unlimited # coredumps
|
||||
make update-stage0 && make -j4
|
||||
if: matrix.name == 'Linux' && needs.configure.outputs.quick == 'false'
|
||||
- name: CCache stats
|
||||
run: ccache -s
|
||||
- name: Show stacktrace for coredumps
|
||||
if: ${{ failure() && matrix.os == 'ubuntu-latest' }}
|
||||
run: |
|
||||
for c in coredumps/*; do
|
||||
progbin="$(file $c | sed "s/.*execfn: '\([^']*\)'.*/\1/")"
|
||||
echo bt | $GDB/bin/gdb -q $progbin $c || true
|
||||
done
|
||||
- name: Upload coredumps
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ failure() && matrix.os == 'ubuntu-latest' }}
|
||||
with:
|
||||
name: coredumps-${{ matrix.name }}
|
||||
path: |
|
||||
./coredumps
|
||||
./build/stage0/bin/lean
|
||||
./build/stage0/lib/lean/libleanshared.so
|
||||
./build/stage1/bin/lean
|
||||
./build/stage1/lib/lean/libleanshared.so
|
||||
./build/stage2/bin/lean
|
||||
./build/stage2/lib/lean/libleanshared.so
|
||||
|
||||
# This job collects results from all the matrix jobs
|
||||
# This can be made the "required" job, instead of listing each
|
||||
# This can be made the “required” job, instead of listing each
|
||||
# matrix job separately
|
||||
all-done:
|
||||
name: Build matrix complete
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
# mark as merely cancelled not failed if builds are cancelled
|
||||
if: ${{ !cancelled() }}
|
||||
if: ${{ always() }}
|
||||
steps:
|
||||
- if: ${{ contains(needs.*.result, 'failure') && github.repository == 'leanprover/lean4' && github.ref_name == 'master' }}
|
||||
uses: zulip/github-actions-zulip/send-message@v1
|
||||
with:
|
||||
api-key: ${{ secrets.ZULIP_BOT_KEY }}
|
||||
email: "github-actions-bot@lean-fro.zulipchat.com"
|
||||
organization-url: "https://lean-fro.zulipchat.com"
|
||||
to: "infrastructure"
|
||||
topic: "Github actions"
|
||||
type: "stream"
|
||||
content: |
|
||||
A build of `${{ github.ref_name }}`, triggered by event `${{ github.event_name }}`, [failed](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}).
|
||||
- if: contains(needs.*.result, 'failure')
|
||||
uses: actions/github-script@v8
|
||||
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Some jobs failed')
|
||||
|
||||
|
||||
# This job creates releases from tags
|
||||
# (whether they are "unofficial" releases for experiments, or official releases when the tag is "v" followed by a semver string.)
|
||||
# We do not attempt to automatically construct a changelog here:
|
||||
# unofficial releases don't need them, and official release notes will be written by a human.
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
prerelease: ${{ !startsWith(github.ref, 'refs/tags/v') || contains(github.ref, '-rc') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Update release.lean-lang.org
|
||||
run: |
|
||||
gh workflow -R leanprover/release-index run update-index.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_INDEX_TOKEN }}
|
||||
|
||||
# This job creates nightly releases during the cron job.
|
||||
# It is responsible for creating the tag, and automatically generating a changelog.
|
||||
@@ -486,14 +478,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# needed for tagging
|
||||
fetch-depth: 0
|
||||
# Doesn't seem to be working when additionally fetching from lean4-nightly
|
||||
#filter: tree:0
|
||||
token: ${{ secrets.PUSH_NIGHTLY_TOKEN }}
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Prepare Nightly Release
|
||||
@@ -503,7 +493,7 @@ jobs:
|
||||
git tag "${{ needs.configure.outputs.nightly }}"
|
||||
git push nightly "${{ needs.configure.outputs.nightly }}"
|
||||
git push -f origin refs/tags/${{ needs.configure.outputs.nightly }}:refs/heads/nightly
|
||||
last_tag="$(git log HEAD^ --simplify-by-decoration --pretty="format:%d" | grep -o "nightly-[^ ,)]*" | head -n 1)"
|
||||
last_tag="$(git log HEAD^ --simplify-by-decoration --pretty="format:%d" | grep -o "nightly-[-0-9]*" | head -n 1)"
|
||||
echo -e "*Changes since ${last_tag}:*\n\n" > diff.md
|
||||
git show "$last_tag":RELEASES.md > old.md
|
||||
#./script/diff_changelogs.py old.md doc/changes.md >> diff.md
|
||||
@@ -511,7 +501,7 @@ jobs:
|
||||
echo -e "\n*Full commit log*\n" >> diff.md
|
||||
git log --oneline "$last_tag"..HEAD | sed 's/^/* /' >> diff.md
|
||||
- name: Release Nightly
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body_path: diff.md
|
||||
prerelease: true
|
||||
@@ -521,23 +511,3 @@ jobs:
|
||||
repository: ${{ github.repository_owner }}/lean4-nightly
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.PUSH_NIGHTLY_TOKEN }}
|
||||
- name: Update release.lean-lang.org
|
||||
run: |
|
||||
gh workflow -R leanprover/release-index run update-index.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_INDEX_TOKEN }}
|
||||
- name: Generate mathlib nightly-testing app token
|
||||
id: mathlib-app-token
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||
continue-on-error: true
|
||||
with:
|
||||
app-id: ${{ secrets.MATHLIB_NIGHTLY_TESTING_APP_ID }}
|
||||
private-key: ${{ secrets.MATHLIB_NIGHTLY_TESTING_PRIVATE_KEY }}
|
||||
owner: leanprover-community
|
||||
repositories: mathlib4-nightly-testing
|
||||
- name: Update toolchain on mathlib4's nightly-testing branch
|
||||
if: steps.mathlib-app-token.outcome == 'success'
|
||||
run: |
|
||||
gh workflow -R leanprover-community/mathlib4-nightly-testing run nightly_bump_and_merge.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.mathlib-app-token.outputs.token }}
|
||||
|
||||
20
.github/workflows/copyright-header.yml
vendored
20
.github/workflows/copyright-header.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Check for copyright header
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
check-lean-files:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Verify .lean files start with a copyright header.
|
||||
run: |
|
||||
FILES=$(find ./src -type d \( -path "./src/lake/examples" -o -path "./src/lake/tests" \) -prune -o -type f -name "*.lean" -exec perl -ne 'BEGIN { $/ = undef; } print "$ARGV\n" if !m{\A/-\nCopyright}; exit;' {} \;)
|
||||
if [ -n "$FILES" ]; then
|
||||
echo "Found .lean files which do not have a copyright header:"
|
||||
echo "$FILES"
|
||||
exit 1
|
||||
else
|
||||
echo "All copyright headers present."
|
||||
fi
|
||||
162
.github/workflows/grove.yml
vendored
162
.github/workflows/grove.yml
vendored
@@ -1,162 +0,0 @@
|
||||
name: Grove
|
||||
|
||||
on:
|
||||
workflow_run: # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_run
|
||||
workflows: [CI]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
grove-build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.workflow_run.conclusion == 'success' && github.repository == 'leanprover/lean4'
|
||||
|
||||
steps:
|
||||
- name: Retrieve information about the original workflow
|
||||
uses: potiuk/get-workflow-origin@v1_1 # https://github.com/marketplace/actions/get-workflow-origin
|
||||
# This action is deprecated and archived, but it seems hard to find a
|
||||
# better solution for getting the PR number
|
||||
# see https://github.com/orgs/community/discussions/25220 for some discussion
|
||||
id: workflow-info
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
sourceRunId: ${{ github.event.workflow_run.id }}
|
||||
|
||||
- name: Check if should run
|
||||
id: should-run
|
||||
run: |
|
||||
# Check if it's a push to master (no PR number and target branch is master)
|
||||
if [ -z "${{ steps.workflow-info.outputs.pullRequestNumber }}" ]; then
|
||||
if [ "${{ github.event.workflow_run.head_branch }}" = "master" ]; then
|
||||
echo "Push to master detected. Running Grove."
|
||||
echo "should-run=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "Push to non-master branch, skipping"
|
||||
echo "should-run=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
else
|
||||
# Check if it's a PR with grove label
|
||||
PR_LABELS='${{ steps.workflow-info.outputs.pullRequestLabels }}'
|
||||
if echo "$PR_LABELS" | grep -q '"grove"'; then
|
||||
echo "PR with grove label detected. Running Grove."
|
||||
echo "should-run=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "PR without grove label, skipping"
|
||||
echo "should-run=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Fetch upstream invalidated facts
|
||||
if: ${{ steps.should-run.outputs.should-run == 'true' && steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
id: fetch-upstream
|
||||
uses: TwoFx/grove-action/fetch-upstream@v0.5
|
||||
with:
|
||||
artifact-name: grove-invalidated-facts
|
||||
base-ref: master
|
||||
|
||||
- name: Download toolchain for this commit
|
||||
if: ${{ steps.should-run.outputs.should-run == 'true' }}
|
||||
id: download-toolchain
|
||||
uses: dawidd6/action-download-artifact@v11
|
||||
with:
|
||||
commit: ${{ steps.workflow-info.outputs.sourceHeadSha }}
|
||||
workflow: ci.yml
|
||||
path: artifacts
|
||||
name: "build-Linux release"
|
||||
allow_forks: true
|
||||
name_is_regexp: true
|
||||
|
||||
- name: Unpack toolchain
|
||||
if: ${{ steps.should-run.outputs.should-run == 'true' }}
|
||||
id: unpack-toolchain
|
||||
run: |
|
||||
cd artifacts
|
||||
# Find the tar.zst file
|
||||
TAR_FILE=$(find . -name "lean-*.tar.zst" -type f | head -1)
|
||||
if [ -z "$TAR_FILE" ]; then
|
||||
echo "Error: No lean-*.tar.zst file found"
|
||||
exit 1
|
||||
fi
|
||||
echo "Found archive: $TAR_FILE"
|
||||
|
||||
# Extract the archive
|
||||
tar --zstd -xf "$TAR_FILE"
|
||||
|
||||
# Find the extracted directory name
|
||||
LEAN_DIR=$(find . -maxdepth 1 -name "lean-*" -type d | head -1)
|
||||
if [ -z "$LEAN_DIR" ]; then
|
||||
echo "Error: No lean-* directory found after extraction"
|
||||
exit 1
|
||||
fi
|
||||
echo "Extracted directory: $LEAN_DIR"
|
||||
echo "lean-dir=$LEAN_DIR" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build
|
||||
if: ${{ steps.should-run.outputs.should-run == 'true' }}
|
||||
id: build
|
||||
uses: TwoFx/grove-action/build@v0.5
|
||||
with:
|
||||
project-path: doc/std/grove
|
||||
script-name: grove-stdlib
|
||||
invalidated-facts-artifact-name: grove-invalidated-facts
|
||||
comment-artifact-name: grove-comment
|
||||
toolchain-id: lean4
|
||||
toolchain-path: artifacts/${{ steps.unpack-toolchain.outputs.lean-dir }}
|
||||
project-ref: ${{ steps.workflow-info.outputs.sourceHeadSha }}
|
||||
|
||||
# deploy-alias computes a URL component for the PR preview. This
|
||||
# is so we can have a stable name to use for feedback on draft
|
||||
# material.
|
||||
- id: deploy-alias
|
||||
if: ${{ steps.should-run.outputs.should-run == 'true' }}
|
||||
uses: actions/github-script@v8
|
||||
name: Compute Alias
|
||||
with:
|
||||
result-encoding: string
|
||||
script: |
|
||||
if (process.env.PR) {
|
||||
return `pr-${process.env.PR}`
|
||||
} else {
|
||||
return 'deploy-preview-main';
|
||||
}
|
||||
env:
|
||||
PR: ${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
|
||||
- name: Deploy to Netlify
|
||||
if: ${{ steps.should-run.outputs.should-run == 'true' }}
|
||||
id: deploy-draft
|
||||
uses: nwtgck/actions-netlify@v3.0
|
||||
with:
|
||||
publish-dir: ${{ steps.build.outputs.out-path }}
|
||||
production-deploy: false
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
alias: ${{ steps.deploy-alias.outputs.result }}
|
||||
enable-commit-comment: false
|
||||
enable-pull-request-comment: false
|
||||
fails-without-credentials: true
|
||||
enable-github-deployment: false
|
||||
enable-commit-status: false
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: "1cacfa39-a11c-467c-99e7-2e01d7b4089e"
|
||||
|
||||
# actions-netlify cannot add deploy links to a PR because it assumes a
|
||||
# pull_request context, not a workflow_run context, see
|
||||
# https://github.com/nwtgck/actions-netlify/issues/545
|
||||
# We work around by using a comment to post the latest link
|
||||
- name: "Comment on PR with preview links"
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
if: ${{ steps.should-run.outputs.should-run == 'true' && steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
with:
|
||||
number: ${{ env.PR_NUMBER }}
|
||||
header: preview-comment
|
||||
recreate: true
|
||||
message: |
|
||||
[Grove](${{ steps.deploy-draft.outputs.deploy-url }}) for revision ${{ steps.workflow-info.outputs.sourceHeadSha }}.
|
||||
|
||||
${{ steps.build.outputs.comment-text }}
|
||||
env:
|
||||
PR_NUMBER: ${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
PR_HEADSHA: ${{ steps.workflow-info.outputs.sourceHeadSha }}
|
||||
34
.github/workflows/jira.yml
vendored
34
.github/workflows/jira.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: Jira sync
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
jira-sync:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Move Jira issue to Done
|
||||
env:
|
||||
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
|
||||
JIRA_USERNAME: ${{ secrets.JIRA_USERNAME }}
|
||||
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
|
||||
run: |
|
||||
issue_number=${{ github.event.issue.number }}
|
||||
|
||||
jira_issue_key=$(curl -s -u "${JIRA_USERNAME}:${JIRA_API_TOKEN}" \
|
||||
-X GET -H "Content-Type: application/json" \
|
||||
"${JIRA_BASE_URL}/rest/api/2/search?jql=summary~\"${issue_number}\"" | \
|
||||
jq -r '.issues[0].key')
|
||||
|
||||
if [ -z "$jira_issue_key" ]; then
|
||||
exit
|
||||
fi
|
||||
|
||||
curl -s -u "${JIRA_USERNAME}:${JIRA_API_TOKEN}" \
|
||||
-X POST -H "Content-Type: application/json" \
|
||||
--data "{\"transition\": {\"id\": \"41\"}}" \
|
||||
"${JIRA_BASE_URL}/rest/api/2/issue/${jira_issue_key}/transitions"
|
||||
|
||||
echo "Moved Jira issue ${jira_issue_key} to Done"
|
||||
40
.github/workflows/labels-from-comments.yml
vendored
40
.github/workflows/labels-from-comments.yml
vendored
@@ -1,8 +1,6 @@
|
||||
# This workflow allows any user to add one of the `awaiting-review`, `awaiting-author`, `WIP`,
|
||||
# `release-ci`, `lake-ci`, or a `changelog-XXX` label by commenting on the PR or issue.
|
||||
# If any labels from the set {`awaiting-review`, `awaiting-author`, `WIP`} are added, other labels
|
||||
# from that set are removed automatically at the same time.
|
||||
# Similarly, if any `changelog-XXX` label is added, other `changelog-YYY` labels are removed.
|
||||
# This workflow allows any user to add one of the `awaiting-review`, `awaiting-author`, or `WIP` labels,
|
||||
# by commenting on the PR or issue.
|
||||
# Other labels from this set are removed automatically at the same time.
|
||||
|
||||
name: Label PR based on Comment
|
||||
|
||||
@@ -12,24 +10,21 @@ on:
|
||||
|
||||
jobs:
|
||||
update-label:
|
||||
if: github.event.issue.pull_request != null && (contains(github.event.comment.body, 'awaiting-review') || contains(github.event.comment.body, 'awaiting-author') || contains(github.event.comment.body, 'WIP') || contains(github.event.comment.body, 'release-ci') || contains(github.event.comment.body, 'lake-ci') || contains(github.event.comment.body, 'changelog-'))
|
||||
if: github.event.issue.pull_request != null && (contains(github.event.comment.body, 'awaiting-review') || contains(github.event.comment.body, 'awaiting-author') || contains(github.event.comment.body, 'WIP'))
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Add label based on comment
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { owner, repo, number: issue_number } = context.issue;
|
||||
const { owner, repo, number: issue_number } = context.issue;
|
||||
const commentLines = context.payload.comment.body.split('\r\n');
|
||||
|
||||
const awaitingReview = commentLines.includes('awaiting-review');
|
||||
const awaitingAuthor = commentLines.includes('awaiting-author');
|
||||
const wip = commentLines.includes('WIP');
|
||||
const releaseCI = commentLines.includes('release-ci');
|
||||
const lakeCI = commentLines.includes('lake-ci');
|
||||
const changelogMatch = commentLines.find(line => line.startsWith('changelog-'));
|
||||
|
||||
if (awaitingReview || awaitingAuthor || wip) {
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: 'awaiting-review' }).catch(() => {});
|
||||
@@ -46,26 +41,3 @@ jobs:
|
||||
if (wip) {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['WIP'] });
|
||||
}
|
||||
|
||||
if (releaseCI) {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['release-ci'] });
|
||||
}
|
||||
if (lakeCI) {
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: ['lake-ci'] });
|
||||
}
|
||||
|
||||
if (changelogMatch) {
|
||||
const changelogLabel = changelogMatch.trim();
|
||||
const { data: existingLabels } = await github.rest.issues.listLabelsOnIssue({ owner, repo, issue_number });
|
||||
const changelogLabels = existingLabels.filter(label => label.name.startsWith('changelog-'));
|
||||
|
||||
// Remove all other changelog labels
|
||||
for (const label of changelogLabels) {
|
||||
if (label.name !== changelogLabel) {
|
||||
await github.rest.issues.removeLabel({ owner, repo, issue_number, name: label.name }).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new changelog label
|
||||
await github.rest.issues.addLabels({ owner, repo, issue_number, labels: [changelogLabel] });
|
||||
}
|
||||
|
||||
135
.github/workflows/nix-ci.yml
vendored
Normal file
135
.github/workflows/nix-ci.yml
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
name: Nix CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
defaults:
|
||||
run:
|
||||
shell: nix run .#ciShell -- bash -euxo pipefail {0}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: Nix Linux
|
||||
os: ubuntu-latest
|
||||
#- name: Nix macOS
|
||||
# os: macos-latest
|
||||
# complete all jobs
|
||||
fail-fast: false
|
||||
name: ${{ matrix.name }}
|
||||
env:
|
||||
NIX_BUILD_ARGS: --print-build-logs --fallback
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# the default is to use a virtual merge commit between the PR and master: just use the PR
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set Up Nix Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: nix-store-cache
|
||||
key: ${{ matrix.name }}-nix-store-cache-${{ github.sha }}
|
||||
# fall back to (latest) previous cache
|
||||
restore-keys: |
|
||||
${{ matrix.name }}-nix-store-cache
|
||||
- name: Further Set Up Nix Cache
|
||||
shell: bash -euxo pipefail {0}
|
||||
run: |
|
||||
# Nix seems to mutate the cache, so make a copy
|
||||
cp -r nix-store-cache nix-store-cache-copy || true
|
||||
- name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
with:
|
||||
extra-conf: |
|
||||
extra-sandbox-paths = /nix/var/cache/ccache?
|
||||
substituters = file://${{ github.workspace }}/nix-store-cache-copy?priority=10&trusted=true https://cache.nixos.org
|
||||
- name: Prepare CCache Cache
|
||||
run: |
|
||||
sudo mkdir -m0770 -p /nix/var/cache/ccache
|
||||
sudo chown -R $USER /nix/var/cache/ccache
|
||||
- name: Setup CCache Cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /nix/var/cache/ccache
|
||||
key: ${{ matrix.name }}-nix-ccache-${{ github.sha }}
|
||||
# fall back to (latest) previous cache
|
||||
restore-keys: |
|
||||
${{ matrix.name }}-nix-ccache
|
||||
- name: Further Set Up CCache Cache
|
||||
run: |
|
||||
sudo chown -R root:nixbld /nix/var/cache
|
||||
sudo chmod -R 770 /nix/var/cache
|
||||
- name: Install Cachix
|
||||
uses: cachix/cachix-action@v12
|
||||
with:
|
||||
name: lean4
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
skipPush: true # we push specific outputs only
|
||||
- name: Build
|
||||
run: |
|
||||
nix build $NIX_BUILD_ARGS .#cacheRoots -o push-build
|
||||
- name: Test
|
||||
run: |
|
||||
nix build $NIX_BUILD_ARGS .#test -o push-test
|
||||
- name: Build manual
|
||||
run: |
|
||||
nix build $NIX_BUILD_ARGS --update-input lean --no-write-lock-file ./doc#{lean-mdbook,leanInk,alectryon,test,inked} -o push-doc
|
||||
nix build $NIX_BUILD_ARGS --update-input lean --no-write-lock-file ./doc
|
||||
# https://github.com/netlify/cli/issues/1809
|
||||
cp -r --dereference ./result ./dist
|
||||
if: matrix.name == 'Nix Linux'
|
||||
- name: Check manual for broken links
|
||||
id: lychee
|
||||
uses: lycheeverse/lychee-action@v1.9.0
|
||||
with:
|
||||
fail: false # report errors but do not block CI on temporary failures
|
||||
# gmplib.org consistently times out from GH actions
|
||||
# the GitHub token is to avoid rate limiting
|
||||
args: --base './dist' --no-progress --github-token ${{ secrets.GITHUB_TOKEN }} --exclude 'gmplib.org' './dist/**/*.html'
|
||||
- name: Push to Cachix
|
||||
run: |
|
||||
[ -z "${{ secrets.CACHIX_AUTH_TOKEN }}" ] || cachix push -j4 lean4 ./push-* || true
|
||||
- name: Rebuild Nix Store Cache
|
||||
run: |
|
||||
rm -rf nix-store-cache || true
|
||||
nix copy ./push-* --to file://$PWD/nix-store-cache?compression=none
|
||||
- id: deploy-info
|
||||
name: Compute Deployment Metadata
|
||||
run: |
|
||||
set -e
|
||||
python3 -c 'import base64; print("alias="+base64.urlsafe_b64encode(bytes.fromhex("${{github.sha}}")).decode("utf-8").rstrip("="))' >> "$GITHUB_OUTPUT"
|
||||
echo "message=`git log -1 --pretty=format:"%s"`" >> "$GITHUB_OUTPUT"
|
||||
- name: Publish manual to Netlify
|
||||
uses: nwtgck/actions-netlify@v2.0
|
||||
id: publish-manual
|
||||
with:
|
||||
publish-dir: ./dist
|
||||
production-branch: master
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
deploy-message: |
|
||||
${{ github.event_name == 'pull_request' && format('pr#{0}: {1}', github.event.number, github.event.pull_request.title) || format('ref/{0}: {1}', github.ref_name, steps.deploy-info.outputs.message) }}
|
||||
alias: ${{ steps.deploy-info.outputs.alias }}
|
||||
enable-commit-comment: false
|
||||
enable-pull-request-comment: false
|
||||
github-deployment-environment: "lean-lang.org/lean4/doc"
|
||||
fails-without-credentials: false
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: "b8e805d2-7e9b-4f80-91fb-a84d72fc4a68"
|
||||
- name: Fixup CCache Cache
|
||||
run: |
|
||||
sudo chown -R $USER /nix/var/cache
|
||||
- name: CCache stats
|
||||
run: CCACHE_DIR=/nix/var/cache/ccache nix run .#nixpkgs.ccache -- -s
|
||||
31
.github/workflows/pr-body.yml
vendored
31
.github/workflows/pr-body.yml
vendored
@@ -1,31 +0,0 @@
|
||||
name: Check PR body for changelog convention
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened, edited, labeled, converted_to_draft, ready_for_review]
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
check-pr-body:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR body
|
||||
if: github.event_name == 'pull_request_target'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
# Safety note: this uses pull_request_target, so the workflow has elevated privileges.
|
||||
# The PR title and body are only used in regex tests (read-only string matching),
|
||||
# never interpolated into shell commands, eval'd, or written to GITHUB_ENV/GITHUB_OUTPUT.
|
||||
script: |
|
||||
const { title, body, labels, draft } = context.payload.pull_request;
|
||||
if (!draft && /^(feat|fix):/.test(title) && !labels.some(label => label.name == "changelog-no")) {
|
||||
if (!labels.some(label => label.name.startsWith("changelog-"))) {
|
||||
core.setFailed('feat/fix PR must have a `changelog-*` label');
|
||||
}
|
||||
if (!/^This PR [^<]/.test(body)) {
|
||||
core.setFailed('feat/fix PR must have changelog summary starting with "This PR ..." as first line.');
|
||||
}
|
||||
}
|
||||
418
.github/workflows/pr-release.yml
vendored
418
.github/workflows/pr-release.yml
vendored
@@ -20,9 +20,7 @@ on:
|
||||
jobs:
|
||||
on-success:
|
||||
runs-on: ubuntu-latest
|
||||
# Run even if CI fails, as long as build artifacts are available
|
||||
# The "Verify release artifacts exist" step will fail if necessary artifacts are missing
|
||||
if: github.event.workflow_run.event == 'pull_request' && github.repository == 'leanprover/lean4'
|
||||
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' && github.repository == 'leanprover/lean4'
|
||||
steps:
|
||||
- name: Retrieve information about the original workflow
|
||||
uses: potiuk/get-workflow-origin@v1_1 # https://github.com/marketplace/actions/get-workflow-origin
|
||||
@@ -36,26 +34,13 @@ jobs:
|
||||
- name: Download artifact from the previous workflow.
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
id: download-artifact
|
||||
uses: dawidd6/action-download-artifact@v11 # https://github.com/marketplace/actions/download-workflow-artifact
|
||||
uses: dawidd6/action-download-artifact@v2 # https://github.com/marketplace/actions/download-workflow-artifact
|
||||
with:
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: artifacts
|
||||
name: build-.*
|
||||
name_is_regexp: true
|
||||
|
||||
# Verify artifacts were downloaded before any side effects (tag creation, release deletion).
|
||||
- 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[@]}"
|
||||
|
||||
- name: Push tag
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
run: |
|
||||
@@ -63,62 +48,34 @@ jobs:
|
||||
git -C lean4.git remote add origin https://github.com/${{ github.repository_owner }}/lean4.git
|
||||
git -C lean4.git fetch -n origin master
|
||||
git -C lean4.git fetch -n origin "${{ steps.workflow-info.outputs.sourceHeadSha }}"
|
||||
|
||||
# Create both the original tag and the SHA-suffixed tag
|
||||
SHORT_SHA="${{ steps.workflow-info.outputs.sourceHeadSha }}"
|
||||
SHORT_SHA="${SHORT_SHA:0:7}"
|
||||
|
||||
# Export the short SHA for use in subsequent steps
|
||||
echo "SHORT_SHA=${SHORT_SHA}" >> "$GITHUB_ENV"
|
||||
|
||||
git -C lean4.git tag -f pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }} "${{ steps.workflow-info.outputs.sourceHeadSha }}"
|
||||
git -C lean4.git tag -f pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-"${SHORT_SHA}" "${{ steps.workflow-info.outputs.sourceHeadSha }}"
|
||||
|
||||
git -C lean4.git remote add pr-releases https://foo:'${{ secrets.PR_RELEASES_TOKEN }}'@github.com/${{ github.repository_owner }}/lean4-pr-releases.git
|
||||
git -C lean4.git push -f pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
git -C lean4.git push -f pr-releases pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-"${SHORT_SHA}"
|
||||
- name: Delete existing releases if present
|
||||
- name: Delete existing release if present
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
run: |
|
||||
# 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.
|
||||
# Try to delete any existing release for the current PR.
|
||||
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 }}
|
||||
# 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)
|
||||
- name: Release
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
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/*/*
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
name: Release for PR ${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# There are coredumps files here as well, but all in deeper subdirectories.
|
||||
files: artifacts/*/*
|
||||
fail_on_unmatched_files: true
|
||||
draft: false
|
||||
tag_name: pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
repository: ${{ github.repository_owner }}/lean4-pr-releases
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
# The token used here must have `workflow` privileges.
|
||||
GITHUB_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
|
||||
- name: Release (SHA-suffixed format)
|
||||
- name: Report release status
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
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:
|
||||
GH_TOKEN: ${{ secrets.PR_RELEASES_TOKEN }}
|
||||
|
||||
- name: Report release status (short format)
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
await github.rest.repos.createCommitStatus({
|
||||
@@ -130,23 +87,9 @@ jobs:
|
||||
description: "${{ github.repository_owner }}/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}",
|
||||
});
|
||||
|
||||
- name: Report release status (SHA-suffixed format)
|
||||
- name: Add label
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: "${{ steps.workflow-info.outputs.sourceHeadSha }}",
|
||||
state: "success",
|
||||
context: "PR toolchain (SHA-suffixed)",
|
||||
description: "${{ github.repository_owner }}/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}",
|
||||
});
|
||||
|
||||
- name: Add toolchain-available label
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.addLabels({
|
||||
@@ -168,22 +111,10 @@ jobs:
|
||||
|
||||
- name: 'Setup jq'
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
uses: dcarbone/install-jq-action@v3.2.0
|
||||
|
||||
# Generate a token for posting comments to Lean PRs about mathlib compatibility.
|
||||
# This app is in the leanprover org and installed on leanprover/lean4.
|
||||
- name: Generate GitHub App token for Lean PR comments
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
id: mathlib-comment-token
|
||||
uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2
|
||||
with:
|
||||
app-id: ${{ secrets.MATHLIB_LEAN_PR_TESTING_APP_ID }}
|
||||
private-key: ${{ secrets.MATHLIB_LEAN_PR_TESTING_PRIVATE_KEY }}
|
||||
owner: leanprover
|
||||
repositories: lean4
|
||||
uses: dcarbone/install-jq-action@v1.0.1
|
||||
|
||||
# Check that the most recently nightly coincides with 'git merge-base HEAD master'
|
||||
- name: Check merge-base and nightly-testing-YYYY-MM-DD for Mathlib/Batteries
|
||||
- name: Check merge-base and nightly-testing-YYYY-MM-DD
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
id: ready
|
||||
run: |
|
||||
@@ -195,7 +126,7 @@ jobs:
|
||||
if [ "$NIGHTLY_SHA" = "$MERGE_BASE_SHA" ]; then
|
||||
echo "The merge base of this PR coincides with the nightly release"
|
||||
|
||||
MATHLIB_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover-community/mathlib4-nightly-testing.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
|
||||
MATHLIB_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover-community/mathlib4.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
|
||||
|
||||
if [[ -n "$MATHLIB_REMOTE_TAGS" ]]; then
|
||||
echo "... and Mathlib has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
@@ -204,32 +135,26 @@ jobs:
|
||||
echo "... but Mathlib does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE="- ❗ Mathlib CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-mathlib\`, Mathlib CI should run now."
|
||||
fi
|
||||
|
||||
STD_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover/std4.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
|
||||
|
||||
if [[ -n "$STD_REMOTE_TAGS" ]]; then
|
||||
echo "... and Std has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE=""
|
||||
else
|
||||
echo "... but Std does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE="- ❗ Std CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-mathlib\`, Std CI should run now."
|
||||
fi
|
||||
|
||||
else
|
||||
echo "The most recently nightly tag on this branch has SHA: $NIGHTLY_SHA"
|
||||
echo "but 'git merge-base origin/master HEAD' reported: $MERGE_BASE_SHA"
|
||||
git -C lean4.git log -10 origin/master
|
||||
|
||||
git -C lean4.git fetch origin nightly-with-mathlib
|
||||
NIGHTLY_WITH_MATHLIB_SHA="$(git -C lean4.git rev-parse "origin/nightly-with-mathlib")"
|
||||
MESSAGE="- ❗ Batteries/Mathlib CI will not be attempted unless your PR branches off the \`nightly-with-mathlib\` branch. Try \`git rebase $MERGE_BASE_SHA --onto $NIGHTLY_WITH_MATHLIB_SHA\`."
|
||||
MESSAGE="- ❗ Std/Mathlib CI will not be attempted unless your PR branches off the \`nightly-with-mathlib\` branch."
|
||||
fi
|
||||
|
||||
if [[ -n "$MESSAGE" ]]; then
|
||||
# Check if force-mathlib-ci label is present
|
||||
# Use GITHUB_TOKEN for read-only label fetch (MATHLIB4_COMMENT_BOT is only for posting comments)
|
||||
LABELS="$(curl --retry 3 --location --silent \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/labels" \
|
||||
| jq -r '.[].name')"
|
||||
|
||||
if echo "$LABELS" | grep -q "^force-mathlib-ci$"; then
|
||||
echo "force-mathlib-ci label detected, forcing CI despite issues"
|
||||
MESSAGE="Forcing Mathlib CI because the \`force-mathlib-ci\` label is present, despite problem: $MESSAGE"
|
||||
FORCE_CI=true
|
||||
else
|
||||
MESSAGE="$MESSAGE You can force Mathlib CI using the \`force-mathlib-ci\` label."
|
||||
fi
|
||||
|
||||
echo "Checking existing messages"
|
||||
|
||||
@@ -238,11 +163,10 @@ jobs:
|
||||
# so keep in sync
|
||||
|
||||
# Use GitHub API to check if a comment already exists
|
||||
existing_comment="$(curl --retry 3 --location --silent \
|
||||
-H "Authorization: token ${{ steps.mathlib-comment-token.outputs.token }}" \
|
||||
existing_comment="$(curl -L -s -H "Authorization: token ${{ secrets.MATHLIB4_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments" \
|
||||
| jq 'first(.[] | select(.body | test("^- . Mathlib") or startswith("Mathlib CI status")) | select(.user.login == "mathlib-lean-pr-testing[bot]"))')"
|
||||
| jq 'first(.[] | select(.body | test("^- . Mathlib") or startswith("Mathlib CI status")) | select(.user.login == "leanprover-community-mathlib4-bot"))')"
|
||||
existing_comment_id="$(echo "$existing_comment" | jq -r .id)"
|
||||
existing_comment_body="$(echo "$existing_comment" | jq -r .body)"
|
||||
|
||||
@@ -252,14 +176,14 @@ jobs:
|
||||
echo "Posting message to the comments: $MESSAGE"
|
||||
|
||||
# Append new result to the existing comment or post a new comment
|
||||
# Use the mathlib-lean-pr-testing app token so Mathlib CI can subsequently edit the comment.
|
||||
# It's essential we use the MATHLIB4_BOT token here, so that Mathlib CI can subsequently edit the comment.
|
||||
if [ -z "$existing_comment_id" ]; then
|
||||
INTRO="Mathlib CI status ([docs](https://leanprover-community.github.io/contribute/tags_and_branches.html)):"
|
||||
# Post new comment with a bullet point
|
||||
echo "Posting as new comment at leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments"
|
||||
curl -L -s \
|
||||
-X POST \
|
||||
-H "Authorization: token ${{ steps.mathlib-comment-token.outputs.token }}" \
|
||||
-H "Authorization: token ${{ secrets.MATHLIB4_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-d "$(jq --null-input --arg intro "$INTRO" --arg val "$MESSAGE" '{"body":($intro + "\n" + $val)}')" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments"
|
||||
@@ -268,7 +192,7 @@ jobs:
|
||||
echo "Appending to existing comment at leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments"
|
||||
curl -L -s \
|
||||
-X PATCH \
|
||||
-H "Authorization: token ${{ steps.mathlib-comment-token.outputs.token }}" \
|
||||
-H "Authorization: token ${{ secrets.MATHLIB4_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-d "$(jq --null-input --arg existing "$existing_comment_body" --arg message "$MESSAGE" '{"body":($existing + "\n" + $message)}')" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/comments/$existing_comment_id"
|
||||
@@ -276,121 +200,14 @@ jobs:
|
||||
else
|
||||
echo "The message already exists in the comment body."
|
||||
fi
|
||||
|
||||
if [[ "$FORCE_CI" == "true" ]]; then
|
||||
echo "mathlib_ready=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "mathlib_ready=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
echo "mathlib_ready=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "mathlib_ready=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Check merge-base and nightly-testing-YYYY-MM-DD for reference manual
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' }}
|
||||
id: reference-manual-ready
|
||||
run: |
|
||||
echo "Most recent nightly release in your branch: $MOST_RECENT_NIGHTLY"
|
||||
NIGHTLY_SHA=$(git -C lean4.git rev-parse "nightly-$MOST_RECENT_NIGHTLY^{commit}")
|
||||
echo "SHA of most recent nightly release: $NIGHTLY_SHA"
|
||||
MERGE_BASE_SHA=$(git -C lean4.git merge-base origin/master "${{ steps.workflow-info.outputs.sourceHeadSha }}")
|
||||
echo "SHA of merge-base: $MERGE_BASE_SHA"
|
||||
if [ "$NIGHTLY_SHA" = "$MERGE_BASE_SHA" ]; then
|
||||
echo "The merge base of this PR coincides with the nightly release"
|
||||
|
||||
MANUAL_REMOTE_TAGS="$(git ls-remote https://github.com/leanprover/reference-manual.git nightly-testing-"$MOST_RECENT_NIGHTLY")"
|
||||
if [[ -n "$MANUAL_REMOTE_TAGS" ]]; then
|
||||
echo "... and the reference manual has a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE=""
|
||||
else
|
||||
echo "... but the reference manual does not yet have a 'nightly-testing-$MOST_RECENT_NIGHTLY' tag."
|
||||
MESSAGE="- ❗ Reference manual CI can not be attempted yet, as the \`nightly-testing-$MOST_RECENT_NIGHTLY\` tag does not exist there yet. We will retry when you push more commits. If you rebase your branch onto \`nightly-with-manual\`, reference manual CI should run now."
|
||||
fi
|
||||
else
|
||||
echo "The most recently nightly tag on this branch has SHA: $NIGHTLY_SHA"
|
||||
echo "but 'git merge-base origin/master HEAD' reported: $MERGE_BASE_SHA"
|
||||
git -C lean4.git log -10 origin/master
|
||||
|
||||
git -C lean4.git fetch origin nightly-with-manual
|
||||
NIGHTLY_WITH_MANUAL_SHA="$(git -C lean4.git rev-parse "origin/nightly-with-manual")"
|
||||
MESSAGE="- ❗ Reference manual CI will not be attempted unless your PR branches off the \`nightly-with-manual\` branch. Try \`git rebase $MERGE_BASE_SHA --onto $NIGHTLY_WITH_MANUAL_SHA\`."
|
||||
fi
|
||||
|
||||
if [[ -n "$MESSAGE" ]]; then
|
||||
# Check if force-manual-ci label is present
|
||||
LABELS="$(curl --retry 3 --location --silent \
|
||||
-H "Authorization: token ${{ secrets.MANUAL_COMMENT_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/labels" \
|
||||
| jq -r '.[].name')"
|
||||
|
||||
if echo "$LABELS" | grep -q "^force-manual-ci$"; then
|
||||
echo "force-manual-ci label detected, forcing CI despite issues"
|
||||
MESSAGE="Forcing reference manual CI because the \`force-manual-ci\` label is present, despite problem: $MESSAGE"
|
||||
FORCE_CI=true
|
||||
else
|
||||
MESSAGE="$MESSAGE You can force reference manual CI using the \`force-manual-ci\` label."
|
||||
fi
|
||||
|
||||
echo "Checking existing messages"
|
||||
|
||||
# The code for updating comments is duplicated in the reference manual's
|
||||
# scripts/lean-pr-testing-comments.sh
|
||||
# so keep in sync
|
||||
|
||||
# Use GitHub API to check if a comment already exists
|
||||
existing_comment="$(curl --retry 3 --location --silent \
|
||||
-H "Authorization: token ${{ secrets.MANUAL_COMMENT_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments" \
|
||||
| jq 'first(.[] | select(.body | test("^- . Manual") or startswith("Reference manual CI status")) | select(.user.login == "leanprover-bot"))')"
|
||||
existing_comment_id="$(echo "$existing_comment" | jq -r .id)"
|
||||
existing_comment_body="$(echo "$existing_comment" | jq -r .body)"
|
||||
|
||||
if [[ "$existing_comment_body" != *"$MESSAGE"* ]]; then
|
||||
MESSAGE="$MESSAGE ($(date "+%Y-%m-%d %H:%M:%S"))"
|
||||
|
||||
echo "Posting message to the comments: $MESSAGE"
|
||||
|
||||
# Append new result to the existing comment or post a new comment
|
||||
# It's essential we use the MANUAL_COMMENT_BOT token here, so that reference manual CI can subsequently edit the comment.
|
||||
if [ -z "$existing_comment_id" ]; then
|
||||
INTRO="Reference manual CI status:"
|
||||
# Post new comment with a bullet point
|
||||
echo "Posting as new comment at leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments"
|
||||
curl -L -s \
|
||||
-X POST \
|
||||
-H "Authorization: token ${{ secrets.MANUAL_COMMENT_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-d "$(jq --null-input --arg intro "$INTRO" --arg val "$MESSAGE" '{"body":($intro + "\n" + $val)}')" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments"
|
||||
else
|
||||
# Append new result to the existing comment
|
||||
echo "Appending to existing comment at leanprover/lean4/issues/${{ steps.workflow-info.outputs.pullRequestNumber }}/comments"
|
||||
curl -L -s \
|
||||
-X PATCH \
|
||||
-H "Authorization: token ${{ secrets.MANUAL_COMMENT_BOT }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-d "$(jq --null-input --arg existing "$existing_comment_body" --arg message "$MESSAGE" '{"body":($existing + "\n" + $message)}')" \
|
||||
"https://api.github.com/repos/leanprover/lean4/issues/comments/$existing_comment_id"
|
||||
fi
|
||||
else
|
||||
echo "The message already exists in the comment body."
|
||||
fi
|
||||
|
||||
if [[ "$FORCE_CI" == "true" ]]; then
|
||||
echo "manual_ready=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "manual_ready=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
else
|
||||
echo "manual_ready=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
|
||||
- name: Report mathlib base
|
||||
if: ${{ steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true' }}
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
const description =
|
||||
@@ -406,40 +223,27 @@ jobs:
|
||||
description: description,
|
||||
});
|
||||
|
||||
# We next automatically create a Batteries branch using this toolchain.
|
||||
# Batteries doesn't itself have a mechanism to report results of CI from this branch back to Lean
|
||||
# Instead this is taken care of by Mathlib CI, which will fail if Batteries fails.
|
||||
|
||||
# Generate a token from the mathlib-nightly-testing GitHub App for cross-org access
|
||||
- name: Generate GitHub App token for leanprover-community repos
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
id: mathlib-app-token
|
||||
uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2
|
||||
with:
|
||||
app-id: ${{ secrets.MATHLIB_NIGHTLY_TESTING_APP_ID }}
|
||||
private-key: ${{ secrets.MATHLIB_NIGHTLY_TESTING_PRIVATE_KEY }}
|
||||
owner: leanprover-community
|
||||
repositories: batteries,mathlib4-nightly-testing
|
||||
|
||||
# We next automatically create a Std branch using this toolchain.
|
||||
# Std doesn't itself have a mechanism to report results of CI from this branch back to Lean
|
||||
# Instead this is taken care of by Mathlib CI, which will fail if Std fails.
|
||||
- name: Cleanup workspace
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
run: |
|
||||
sudo rm -rf ./*
|
||||
|
||||
# Checkout the Batteries repository with all branches
|
||||
- name: Checkout Batteries repository
|
||||
# Checkout the Std repository with all branches
|
||||
- name: Checkout Std repository
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: leanprover-community/batteries
|
||||
token: ${{ steps.mathlib-app-token.outputs.token }}
|
||||
repository: leanprover/std4
|
||||
token: ${{ secrets.MATHLIB4_BOT }}
|
||||
ref: nightly-testing
|
||||
fetch-depth: 0 # This ensures we check out all tags and branches.
|
||||
filter: tree:0
|
||||
|
||||
- name: Check if tag exists
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
id: check_batteries_tag
|
||||
id: check_std_tag
|
||||
run: |
|
||||
git config user.name "leanprover-community-mathlib4-bot"
|
||||
git config user.email "leanprover-community-mathlib4-bot@users.noreply.github.com"
|
||||
@@ -447,7 +251,7 @@ jobs:
|
||||
if git ls-remote --heads --tags --exit-code origin "nightly-testing-${MOST_RECENT_NIGHTLY}" >/dev/null; then
|
||||
BASE="nightly-testing-${MOST_RECENT_NIGHTLY}"
|
||||
else
|
||||
echo "Couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' tag at Batteries. Falling back to 'nightly-testing'."
|
||||
echo "This shouldn't be possible: couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' tag at Std. Falling back to 'nightly-testing'."
|
||||
BASE=nightly-testing
|
||||
fi
|
||||
|
||||
@@ -458,18 +262,16 @@ jobs:
|
||||
if [ "$EXISTS" = "0" ]; then
|
||||
echo "Branch does not exist, creating it."
|
||||
git switch -c lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} "$BASE"
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
git commit --allow-empty -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
git commit -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
else
|
||||
echo "Branch already exists, updating lean-toolchain."
|
||||
echo "Branch already exists, pushing an empty commit."
|
||||
git switch lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# The Batteries `nightly-testing` or `nightly-testing-YYYY-MM-DD` branch may have moved since this branch was created, so merge their changes.
|
||||
# The Std `nightly-testing` or `nightly-testing-YYYY-MM-DD` branch may have moved since this branch was created, so merge their changes.
|
||||
# (This should no longer be possible once `nightly-testing-YYYY-MM-DD` is a tag, but it is still safe to merge.)
|
||||
git merge "$BASE" --strategy-option ours --no-commit --allow-unrelated-histories
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
git commit --allow-empty -m "Update lean-toolchain for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
git commit --allow-empty -m "Trigger CI for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
fi
|
||||
|
||||
- name: Push changes
|
||||
@@ -489,20 +291,12 @@ jobs:
|
||||
# Checkout the mathlib4 repository with all branches
|
||||
- name: Checkout mathlib4 repository
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: leanprover-community/mathlib4-nightly-testing
|
||||
token: ${{ steps.mathlib-app-token.outputs.token }}
|
||||
repository: leanprover-community/mathlib4
|
||||
token: ${{ secrets.MATHLIB4_BOT }}
|
||||
ref: nightly-testing
|
||||
fetch-depth: 0 # This ensures we check out all tags and branches.
|
||||
filter: tree:0
|
||||
|
||||
- name: install elan
|
||||
run: |
|
||||
set -o pipefail
|
||||
curl -sSfL https://github.com/leanprover/elan/releases/download/v3.0.0/elan-x86_64-unknown-linux-gnu.tar.gz | tar xz
|
||||
./elan-init -y --default-toolchain none
|
||||
echo "$HOME/.elan/bin" >> "${GITHUB_PATH}"
|
||||
|
||||
- name: Check if tag exists
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
@@ -514,7 +308,7 @@ jobs:
|
||||
if git ls-remote --heads --tags --exit-code origin "nightly-testing-${MOST_RECENT_NIGHTLY}" >/dev/null; then
|
||||
BASE="nightly-testing-${MOST_RECENT_NIGHTLY}"
|
||||
else
|
||||
echo "Couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' branch at Mathlib. Falling back to 'nightly-testing'."
|
||||
echo "This shouldn't be possible: couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' branch at Mathlib. Falling back to 'nightly-testing'."
|
||||
BASE=nightly-testing
|
||||
fi
|
||||
|
||||
@@ -525,99 +319,21 @@ jobs:
|
||||
if [ "$EXISTS" = "0" ]; then
|
||||
echo "Branch does not exist, creating it."
|
||||
git switch -c lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} "$BASE"
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
sed -i 's,require "leanprover-community" / "batteries" @ git ".\+",require "leanprover-community" / "batteries" @ git "lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}",' lakefile.lean
|
||||
lake update batteries
|
||||
git add lakefile.lean lake-manifest.json
|
||||
git commit --allow-empty -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
sed -i "s/require std from git \"https:\/\/github.com\/leanprover\/std4\" @ \".\+\"/require std from git \"https:\/\/github.com\/leanprover\/std4\" @ \"nightly-testing-${MOST_RECENT_NIGHTLY}\"/" lakefile.lean
|
||||
git add lakefile.lean
|
||||
git commit -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
else
|
||||
echo "Branch already exists, updating lean-toolchain and bumping Batteries."
|
||||
echo "Branch already exists, pushing an empty commit."
|
||||
git switch lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# The Mathlib `nightly-testing` branch or `nightly-testing-YYYY-MM-DD` tag may have moved since this branch was created, so merge their changes.
|
||||
# (This should no longer be possible once `nightly-testing-YYYY-MM-DD` is a tag, but it is still safe to merge.)
|
||||
git merge "$BASE" --strategy-option ours --no-commit --allow-unrelated-histories
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
lake update batteries
|
||||
git add lake-manifest.json
|
||||
git commit --allow-empty -m "Update lean-toolchain for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
git commit --allow-empty -m "Trigger CI for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
fi
|
||||
|
||||
- name: Push changes
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
run: |
|
||||
git push origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
|
||||
- name: Add mathlib4-nightly-available label
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.ready.outputs.mathlib_ready == 'true'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.addLabels({
|
||||
issue_number: ${{ steps.workflow-info.outputs.pullRequestNumber }},
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: ['mathlib4-nightly-available']
|
||||
})
|
||||
|
||||
# We next automatically create a reference manual branch using this toolchain.
|
||||
# Reference manual CI will be responsible for reporting back success or failure
|
||||
# to the PR comments asynchronously (and thus transitively SubVerso/Verso).
|
||||
- name: Cleanup workspace
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.reference-manual-ready.outputs.manual_ready == 'true'
|
||||
run: |
|
||||
sudo rm -rf ./*
|
||||
|
||||
# Checkout the reference manual repository with all branches
|
||||
- name: Checkout mathlib4 repository
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.reference-manual-ready.outputs.manual_ready == 'true'
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: leanprover/reference-manual
|
||||
token: ${{ secrets.MANUAL_PR_BOT }}
|
||||
ref: nightly-testing
|
||||
fetch-depth: 0 # This ensures we check out all tags and branches.
|
||||
filter: tree:0
|
||||
|
||||
- name: Check if tag in reference manual exists
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.reference-manual-ready.outputs.manual_ready == 'true'
|
||||
id: check_manual_tag
|
||||
run: |
|
||||
git config user.name "leanprover-bot"
|
||||
git config user.email "leanprover-bot@lean-fro.org"
|
||||
|
||||
if git ls-remote --heads --tags --exit-code origin "nightly-testing-${MOST_RECENT_NIGHTLY}" >/dev/null; then
|
||||
BASE="nightly-testing-${MOST_RECENT_NIGHTLY}"
|
||||
else
|
||||
echo "Couldn't find a 'nightly-testing-${MOST_RECENT_NIGHTLY}' branch in the reference manual. Falling back to 'nightly-testing'."
|
||||
BASE=nightly-testing
|
||||
fi
|
||||
|
||||
echo "Using base tag: $BASE"
|
||||
|
||||
EXISTS="$(git ls-remote --heads origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} | wc -l)"
|
||||
echo "Branch exists: $EXISTS"
|
||||
if [ "$EXISTS" = "0" ]; then
|
||||
echo "Branch does not exist, creating it."
|
||||
git switch -c lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }} "$BASE"
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
git add lakefile.lean lake-manifest.json
|
||||
git commit --allow-empty -m "Update lean-toolchain for testing https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
else
|
||||
echo "Branch already exists, updating lean-toolchain."
|
||||
git switch lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
# The reference manual's `nightly-testing` branch or `nightly-testing-YYYY-MM-DD` tag may have moved since this branch was created, so merge their changes.
|
||||
# (This should no longer be possible once `nightly-testing-YYYY-MM-DD` is a tag, but it is still safe to merge.)
|
||||
git merge "$BASE" --strategy-option ours --no-commit --allow-unrelated-histories
|
||||
echo "leanprover/lean4-pr-releases:pr-release-${{ steps.workflow-info.outputs.pullRequestNumber }}-${{ env.SHORT_SHA }}" > lean-toolchain
|
||||
git add lean-toolchain
|
||||
git add lake-manifest.json
|
||||
git commit --allow-empty -m "Update lean-toolchain for https://github.com/leanprover/lean4/pull/${{ steps.workflow-info.outputs.pullRequestNumber }}"
|
||||
fi
|
||||
|
||||
- name: Push changes
|
||||
if: steps.workflow-info.outputs.pullRequestNumber != '' && steps.reference-manual-ready.outputs.manual_ready == 'true'
|
||||
run: |
|
||||
git push origin lean-pr-testing-${{ steps.workflow-info.outputs.pullRequestNumber }}
|
||||
|
||||
4
.github/workflows/pr-title.yml
vendored
4
.github/workflows/pr-title.yml
vendored
@@ -10,11 +10,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR title
|
||||
uses: actions/github-script@v8
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const msg = context.payload.pull_request? context.payload.pull_request.title : context.payload.merge_group.head_commit.message;
|
||||
console.log(`Message: ${msg}`)
|
||||
if (!/^(feat|fix|doc|style|refactor|test|chore|perf): (?![A-Z][a-z]).*[^.]($|\n\n)/.test(msg)) {
|
||||
if (!/^(feat|fix|doc|style|refactor|test|chore|perf): .*[^.]($|\n\n)/.test(msg)) {
|
||||
core.setFailed('PR title does not follow the Commit Convention (https://leanprover.github.io/lean4/doc/dev/commit_convention.html).');
|
||||
}
|
||||
|
||||
35
.github/workflows/restart-on-label.yml
vendored
35
.github/workflows/restart-on-label.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Restart by label
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- unlabeled
|
||||
- labeled
|
||||
jobs:
|
||||
restart-on-label:
|
||||
runs-on: ubuntu-latest
|
||||
if: contains(github.event.label.name, 'merge-ci') || contains(github.event.label.name, 'release-ci') || contains(github.event.label.name, 'lake-ci')
|
||||
steps:
|
||||
- run: |
|
||||
# Finding latest CI workflow run on current pull request
|
||||
# (unfortunately cannot search by PR number, only base branch,
|
||||
# and that is't even unique given PRs from forks, but the risk
|
||||
# of confusion is low and the danger is mild)
|
||||
echo "Trying to find a run with branch $head_ref and commit $head_sha"
|
||||
run_id="$(gh run list -e pull_request -b "$head_ref" -c "$head_sha" \
|
||||
--workflow 'CI' --limit 1 --json databaseId --jq '.[0].databaseId')"
|
||||
echo "Run id: ${run_id}"
|
||||
gh run view "$run_id"
|
||||
echo "Cancelling (just in case)"
|
||||
gh run cancel "$run_id" || echo "(failed)"
|
||||
echo "Waiting for 30s"
|
||||
sleep 30
|
||||
gh run view "$run_id"
|
||||
echo "Rerunning"
|
||||
gh run rerun "$run_id"
|
||||
gh run view "$run_id"
|
||||
shell: bash
|
||||
env:
|
||||
head_ref: ${{ github.head_ref }}
|
||||
head_sha: ${{ github.event.pull_request.head.sha }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
days-before-stale: -1
|
||||
days-before-pr-stale: 30
|
||||
|
||||
53
.github/workflows/update-stage0.yml
vendored
53
.github/workflows/update-stage0.yml
vendored
@@ -18,16 +18,12 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
update-stage0:
|
||||
runs-on: nscloud-ubuntu-22.04-amd64-8x16
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
CCACHE_COMPRESS: true
|
||||
CCACHE_MAXSIZE: 400M
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# This action should push to an otherwise protected branch, so it
|
||||
# uses a deploy key with write permissions, as suggested at
|
||||
# https://stackoverflow.com/a/76135647/946226
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ssh-key: ${{secrets.STAGE0_SSH_KEY}}
|
||||
- run: echo "should_update_stage0=yes" >> "$GITHUB_ENV"
|
||||
@@ -45,44 +41,19 @@ jobs:
|
||||
git config --global user.name "Lean stage0 autoupdater"
|
||||
git config --global user.email "<>"
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
name: Install Nix
|
||||
uses: DeterminateSystems/nix-installer-action@main
|
||||
- name: Open Nix shell once
|
||||
if: env.should_update_stage0 == 'yes'
|
||||
run: true
|
||||
shell: 'nix develop -c bash -euxo pipefail {0}'
|
||||
- name: Set up NPROC
|
||||
if: env.should_update_stage0 == 'yes'
|
||||
run: |
|
||||
echo "NPROC=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)" >> $GITHUB_ENV
|
||||
shell: 'nix develop -c bash -euxo pipefail {0}'
|
||||
- name: Restore Cache
|
||||
if: env.should_update_stage0 == 'yes'
|
||||
uses: actions/cache/restore@v5
|
||||
# Would be nice, but does not work yet:
|
||||
# https://github.com/DeterminateSystems/magic-nix-cache/issues/39
|
||||
# This action does not run that often and building runs in a few minutes, so ok for now
|
||||
#- if: env.should_update_stage0 == 'yes'
|
||||
# uses: DeterminateSystems/magic-nix-cache-action@v2
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
name: Install Cachix
|
||||
uses: cachix/cachix-action@v12
|
||||
with:
|
||||
# NOTE: must be in sync with `restore-cache` in `build-template.yml`
|
||||
path: |
|
||||
.ccache
|
||||
build/stage1/**/*.trace
|
||||
build/stage1/**/*.olean*
|
||||
build/stage1/**/*.ilean
|
||||
build/stage1/**/*.ir
|
||||
build/stage1/**/*.c
|
||||
build/stage1/**/*.c.o*
|
||||
key: Linux Lake-build-v4-${{ github.sha }}
|
||||
# fall back to (latest) previous cache
|
||||
restore-keys: |
|
||||
Linux Lake-build-v4
|
||||
name: lean4
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
# sync options with `Linux Lake` to ensure cache reuse
|
||||
run: |
|
||||
mkdir -p build
|
||||
cmake --preset release -B build -DLEAN_EXTRA_MAKE_OPTS=-DwarningAsError=true
|
||||
shell: 'nix develop -c bash -euxo pipefail {0}'
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
run: |
|
||||
make -j$NPROC -C build update-stage0-commit
|
||||
shell: 'nix develop -c bash -euxo pipefail {0}'
|
||||
run: nix run .#update-stage0-commit
|
||||
- if: env.should_update_stage0 == 'yes'
|
||||
run: git show --stat
|
||||
- if: env.should_update_stage0 == 'yes' && github.event_name == 'push'
|
||||
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,11 +1,11 @@
|
||||
*~
|
||||
\#*
|
||||
.#*
|
||||
*.lock
|
||||
.lake
|
||||
lake-manifest.json
|
||||
/build
|
||||
/src/lakefile.toml
|
||||
/lakefile.toml
|
||||
build
|
||||
!/src/lake/Lake/Build
|
||||
GPATH
|
||||
GRTAGS
|
||||
GSYMS
|
||||
@@ -17,13 +17,9 @@ compile_commands.json
|
||||
*.idea
|
||||
tasks.json
|
||||
settings.json
|
||||
!.claude/settings.json
|
||||
.gdb_history
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/extensions.json
|
||||
script/__pycache__
|
||||
*.produced.out
|
||||
CMakeSettings.json
|
||||
CppProperties.json
|
||||
@@ -33,4 +29,3 @@ fwOut.txt
|
||||
wdErr.txt
|
||||
wdIn.txt
|
||||
wdOut.txt
|
||||
downstream_releases/
|
||||
|
||||
14
.gitpod.Dockerfile
vendored
14
.gitpod.Dockerfile
vendored
@@ -1,14 +0,0 @@
|
||||
# You can find the new timestamped tags here: https://hub.docker.com/r/gitpod/workspace-full/tags
|
||||
FROM gitpod/workspace-full
|
||||
|
||||
USER root
|
||||
RUN apt-get update && apt-get install git libgmp-dev libuv1-dev cmake ccache clang -y && apt-get clean
|
||||
|
||||
USER gitpod
|
||||
|
||||
# Install and configure elan
|
||||
RUN curl https://raw.githubusercontent.com/leanprover/elan/master/elan-init.sh -sSf | sh -s -- -y --default-toolchain none
|
||||
ENV PATH="/home/gitpod/.elan/bin:${PATH}"
|
||||
# Create a dummy toolchain so that we can pre-register it with elan
|
||||
RUN mkdir -p /workspace/lean4/build/release/stage1/bin && touch /workspace/lean4/build/release/stage1/bin/lean && elan toolchain link lean4 /workspace/lean4/build/release/stage1
|
||||
RUN mkdir -p /workspace/lean4/build/release/stage0/bin && touch /workspace/lean4/build/release/stage0/bin/lean && elan toolchain link lean4-stage0 /workspace/lean4/build/release/stage0
|
||||
11
.gitpod.yml
11
.gitpod.yml
@@ -1,11 +0,0 @@
|
||||
image:
|
||||
file: .gitpod.Dockerfile
|
||||
|
||||
vscode:
|
||||
extensions:
|
||||
- leanprover.lean4
|
||||
|
||||
tasks:
|
||||
- name: Release build
|
||||
init: cmake --preset release
|
||||
command: make -C build/release -j$(nproc || sysctl -n hw.logicalcpu)
|
||||
5
.vscode/extensions.json
vendored
5
.vscode/extensions.json
vendored
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"leanprover.lean4"
|
||||
]
|
||||
}
|
||||
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
// These require the CMake Tools extension (ms-vscode.cmake-tools).
|
||||
"cmake.buildDirectory": "${workspaceFolder}/build/release",
|
||||
"cmake.generator": "Unix Makefiles",
|
||||
"[lean4]": {
|
||||
"editor.rulers": [
|
||||
100
|
||||
]
|
||||
}
|
||||
}
|
||||
34
.vscode/tasks.json
vendored
34
.vscode/tasks.json
vendored
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"type": "shell",
|
||||
"command": "make -C build/release -j$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4)",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "build-old",
|
||||
"type": "shell",
|
||||
"command": "make -C build/release -j$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4) LAKE_EXTRA_ARGS=--old",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "build"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "test",
|
||||
"type": "shell",
|
||||
"command": "NPROC=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/null || echo 4); CTEST_OUTPUT_ON_FAILURE=1 make -C build/release test -j$NPROC ARGS=\"-j$NPROC\"",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
210
CMakeLists.txt
210
CMakeLists.txt
@@ -1,35 +1,24 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
|
||||
if(NOT CMAKE_GENERATOR MATCHES "Makefiles")
|
||||
message(FATAL_ERROR "Only makefile generators are supported")
|
||||
endif()
|
||||
|
||||
option(USE_MIMALLOC "use mimalloc" ON)
|
||||
|
||||
cmake_minimum_required(VERSION 3.11)
|
||||
# store all variables passed on the command line into CL_ARGS so we can pass them to the stage builds
|
||||
# https://stackoverflow.com/a/48555098/161659
|
||||
# MUST be done before call to 'project'
|
||||
# Use standard release build (discarding LEAN_EXTRA_CXX_FLAGS etc.) for stage0 by default since it is assumed to be "good", but still pass through CMake platform arguments (compiler, toolchain file, ..).
|
||||
# Use standard release build (discarding LEAN_CXX_EXTRA_FLAGS etc.) for stage0 by default since it is assumed to be "good", but still pass through CMake platform arguments (compiler, toolchain file, ..).
|
||||
# Use `STAGE0_` prefix to pass variables to stage0 explicitly.
|
||||
get_cmake_property(vars CACHE_VARIABLES)
|
||||
foreach(var ${vars})
|
||||
get_property(currentHelpString CACHE "${var}" PROPERTY HELPSTRING)
|
||||
if(var MATCHES "STAGE0_(.*)")
|
||||
if("${var}" MATCHES "STAGE0_(.*)")
|
||||
list(APPEND STAGE0_ARGS "-D${CMAKE_MATCH_1}=${${var}}")
|
||||
elseif(var MATCHES "STAGE1_(.*)")
|
||||
list(APPEND STAGE1_ARGS "-D${CMAKE_MATCH_1}=${${var}}")
|
||||
elseif(currentHelpString MATCHES "No help, variable specified on the command line." OR currentHelpString STREQUAL "")
|
||||
elseif("${currentHelpString}" MATCHES "No help, variable specified on the command line." OR "${currentHelpString}" STREQUAL "")
|
||||
list(APPEND CL_ARGS "-D${var}=${${var}}")
|
||||
if(var MATCHES "USE_GMP|CHECK_OLEAN_VERSION|LEAN_VERSION_.*|LEAN_SPECIAL_VERSION_DESC")
|
||||
if("${var}" MATCHES "USE_GMP|CHECK_OLEAN_VERSION")
|
||||
# must forward options that generate incompatible .olean format
|
||||
list(APPEND STAGE0_ARGS "-D${var}=${${var}}")
|
||||
elseif(var MATCHES "LLVM*|PKG_CONFIG|USE_LAKE|USE_MIMALLOC")
|
||||
endif()
|
||||
if("${var}" MATCHES "LLVM*")
|
||||
list(APPEND STAGE0_ARGS "-D${var}=${${var}}")
|
||||
endif()
|
||||
elseif(var MATCHES "USE_MIMALLOC")
|
||||
list(APPEND CL_ARGS "-D${var}=${${var}}")
|
||||
list(APPEND STAGE0_ARGS "-D${var}=${${var}}")
|
||||
elseif((var MATCHES "CMAKE_.*") AND NOT (var MATCHES "CMAKE_BUILD_TYPE") AND NOT (var MATCHES "CMAKE_HOME_DIRECTORY"))
|
||||
elseif(("${var}" MATCHES "CMAKE_.*") AND NOT ("${var}" MATCHES "CMAKE_BUILD_TYPE") AND NOT ("${var}" MATCHES "CMAKE_HOME_DIRECTORY"))
|
||||
list(APPEND PLATFORM_ARGS "-D${var}=${${var}}")
|
||||
endif()
|
||||
endforeach()
|
||||
@@ -38,188 +27,63 @@ include(ExternalProject)
|
||||
project(LEAN CXX C)
|
||||
|
||||
if(NOT (DEFINED STAGE0_CMAKE_EXECUTABLE_SUFFIX))
|
||||
set(STAGE0_CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}")
|
||||
set(STAGE0_CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}")
|
||||
endif()
|
||||
|
||||
# Don't do anything with cadical/leantar on wasm
|
||||
if(NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
|
||||
find_program(CADICAL cadical)
|
||||
if(NOT CADICAL)
|
||||
set(CADICAL_CXX c++)
|
||||
if(CADICAL_USE_CUSTOM_CXX)
|
||||
set(CADICAL_CXX ${CMAKE_CXX_COMPILER})
|
||||
# Use same platform flags as for Lean executables, in particular from `prepare-llvm-linux.sh`,
|
||||
# but not Lean-specific `LEAN_EXTRA_CXX_FLAGS` such as fsanitize.
|
||||
set(CADICAL_CXXFLAGS "${CMAKE_CXX_FLAGS}")
|
||||
set(CADICAL_LDFLAGS "-Wl,-rpath=\\$$ORIGIN/../lib")
|
||||
endif()
|
||||
find_program(CCACHE ccache)
|
||||
if(CCACHE)
|
||||
set(CADICAL_CXX "${CCACHE} ${CADICAL_CXX}")
|
||||
endif()
|
||||
# missing stdio locking API on Windows
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
string(APPEND CADICAL_CXXFLAGS " -DNUNLOCKED")
|
||||
endif()
|
||||
string(APPEND CADICAL_CXXFLAGS " -DNCLOSEFROM")
|
||||
ExternalProject_Add(
|
||||
cadical
|
||||
PREFIX cadical
|
||||
GIT_REPOSITORY https://github.com/arminbiere/cadical
|
||||
GIT_TAG rel-2.1.2
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND
|
||||
$(MAKE) -f ${CMAKE_SOURCE_DIR}/src/cadical.mk CMAKE_EXECUTABLE_SUFFIX=${CMAKE_EXECUTABLE_SUFFIX}
|
||||
CXX=${CADICAL_CXX} CXXFLAGS=${CADICAL_CXXFLAGS} LDFLAGS=${CADICAL_LDFLAGS}
|
||||
BUILD_IN_SOURCE ON
|
||||
INSTALL_COMMAND ""
|
||||
)
|
||||
set(CADICAL ${CMAKE_BINARY_DIR}/cadical/cadical${CMAKE_EXECUTABLE_SUFFIX})
|
||||
list(APPEND EXTRA_DEPENDS cadical)
|
||||
endif()
|
||||
find_program(LEANTAR leantar)
|
||||
if(NOT LEANTAR)
|
||||
set(LEANTAR_VERSION v0.1.19)
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
set(LEANTAR_ARCHIVE_SUFFIX .zip)
|
||||
set(LEANTAR_TARGET x86_64-pc-windows-msvc)
|
||||
else()
|
||||
set(LEANTAR_ARCHIVE_SUFFIX .tar.gz)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64")
|
||||
set(LEANTAR_TARGET_ARCH aarch64)
|
||||
else()
|
||||
set(LEANTAR_TARGET_ARCH x86_64)
|
||||
endif()
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
set(LEANTAR_TARGET_OS apple-darwin)
|
||||
else()
|
||||
set(LEANTAR_TARGET_OS unknown-linux-musl)
|
||||
endif()
|
||||
set(LEANTAR_TARGET ${LEANTAR_TARGET_ARCH}-${LEANTAR_TARGET_OS})
|
||||
endif()
|
||||
set(
|
||||
LEANTAR
|
||||
${CMAKE_BINARY_DIR}/leantar/leantar-${LEANTAR_VERSION}-${LEANTAR_TARGET}/leantar${CMAKE_EXECUTABLE_SUFFIX}
|
||||
)
|
||||
if(NOT EXISTS "${LEANTAR}")
|
||||
file(
|
||||
DOWNLOAD
|
||||
https://github.com/digama0/leangz/releases/download/${LEANTAR_VERSION}/leantar-${LEANTAR_VERSION}-${LEANTAR_TARGET}${LEANTAR_ARCHIVE_SUFFIX}
|
||||
${CMAKE_BINARY_DIR}/leantar${LEANTAR_ARCHIVE_SUFFIX}
|
||||
)
|
||||
file(
|
||||
ARCHIVE_EXTRACT
|
||||
INPUT ${CMAKE_BINARY_DIR}/leantar${LEANTAR_ARCHIVE_SUFFIX}
|
||||
DESTINATION ${CMAKE_BINARY_DIR}/leantar
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
list(APPEND CL_ARGS -DCADICAL=${CADICAL} -DLEANTAR=${LEANTAR})
|
||||
endif()
|
||||
|
||||
if(USE_MIMALLOC)
|
||||
ExternalProject_Add(
|
||||
mimalloc
|
||||
PREFIX mimalloc
|
||||
GIT_REPOSITORY https://github.com/microsoft/mimalloc
|
||||
GIT_TAG v2.2.3
|
||||
# just download, we compile it as part of each stage as it is small
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
)
|
||||
list(APPEND EXTRA_DEPENDS mimalloc)
|
||||
endif()
|
||||
|
||||
if(NOT STAGE1_PREV_STAGE)
|
||||
ExternalProject_Add(
|
||||
stage0
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}/stage0"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage0
|
||||
# do not rebuild stage0 when git hash changes; it's not from this commit anyway
|
||||
# (however, CI will override this as we need to embed the githash into the stage 1 library built
|
||||
# by stage 0)
|
||||
CMAKE_ARGS -DSTAGE=0 -DUSE_GITHASH=OFF ${PLATFORM_ARGS} ${STAGE0_ARGS}
|
||||
BUILD_ALWAYS
|
||||
ON # cmake doesn't auto-detect changes without a download method
|
||||
INSTALL_COMMAND
|
||||
"" # skip install
|
||||
DEPENDS ${EXTRA_DEPENDS}
|
||||
)
|
||||
list(APPEND EXTRA_DEPENDS stage0)
|
||||
endif()
|
||||
ExternalProject_Add(
|
||||
stage1
|
||||
ExternalProject_add(stage0
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}/stage0"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage0
|
||||
# do not rebuild stage0 when git hash changes; it's not from this commit anyway
|
||||
# (however, `CHECK_OLEAN_VERSION=ON` in CI will override this as we need to
|
||||
# embed the githash into the stage 1 library built by stage 0)
|
||||
CMAKE_ARGS -DSTAGE=0 -DUSE_GITHASH=OFF ${PLATFORM_ARGS} ${STAGE0_ARGS}
|
||||
BUILD_ALWAYS ON # cmake doesn't auto-detect changes without a download method
|
||||
INSTALL_COMMAND "" # skip install
|
||||
DEPENDS ${EXTRA_DEPENDS}
|
||||
)
|
||||
ExternalProject_add(stage1
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage1
|
||||
CMAKE_ARGS
|
||||
-DSTAGE=1 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage0
|
||||
-DPREV_STAGE_CMAKE_EXECUTABLE_SUFFIX=${STAGE0_CMAKE_EXECUTABLE_SUFFIX} ${CL_ARGS} ${STAGE1_ARGS}
|
||||
CMAKE_ARGS -DSTAGE=1 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage0 -DPREV_STAGE_CMAKE_EXECUTABLE_SUFFIX=${STAGE0_CMAKE_EXECUTABLE_SUFFIX} ${CL_ARGS}
|
||||
BUILD_ALWAYS ON
|
||||
INSTALL_COMMAND ""
|
||||
DEPENDS ${EXTRA_DEPENDS}
|
||||
STEP_TARGETS configure
|
||||
DEPENDS stage0
|
||||
)
|
||||
ExternalProject_Add(
|
||||
stage2
|
||||
ExternalProject_add(stage2
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage2
|
||||
CMAKE_ARGS
|
||||
-DSTAGE=2 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage1 -DPREV_STAGE_CMAKE_EXECUTABLE_SUFFIX=${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CL_ARGS}
|
||||
CMAKE_ARGS -DSTAGE=2 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage1 -DPREV_STAGE_CMAKE_EXECUTABLE_SUFFIX=${CMAKE_EXECUTABLE_SUFFIX} ${CL_ARGS}
|
||||
BUILD_ALWAYS ON
|
||||
INSTALL_COMMAND ""
|
||||
DEPENDS stage1
|
||||
EXCLUDE_FROM_ALL ON
|
||||
STEP_TARGETS configure
|
||||
)
|
||||
ExternalProject_Add(
|
||||
stage3
|
||||
ExternalProject_add(stage3
|
||||
SOURCE_DIR "${LEAN_SOURCE_DIR}"
|
||||
SOURCE_SUBDIR src
|
||||
BINARY_DIR stage3
|
||||
CMAKE_ARGS
|
||||
-DSTAGE=3 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage2 -DPREV_STAGE_CMAKE_EXECUTABLE_SUFFIX=${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CL_ARGS}
|
||||
CMAKE_ARGS -DSTAGE=3 -DPREV_STAGE=${CMAKE_BINARY_DIR}/stage2 -DPREV_STAGE_CMAKE_EXECUTABLE_SUFFIX=${CMAKE_EXECUTABLE_SUFFIX} ${CL_ARGS}
|
||||
BUILD_ALWAYS ON
|
||||
INSTALL_COMMAND ""
|
||||
DEPENDS stage2
|
||||
EXCLUDE_FROM_ALL ON
|
||||
STEP_TARGETS configure
|
||||
)
|
||||
|
||||
# targets forwarded to appropriate stages
|
||||
|
||||
add_custom_target(update-stage0 COMMAND $(MAKE) -C stage1 update-stage0 DEPENDS stage1)
|
||||
add_custom_target(update-stage0
|
||||
COMMAND $(MAKE) -C stage1 update-stage0
|
||||
DEPENDS stage1)
|
||||
|
||||
add_custom_target(update-stage0-commit COMMAND $(MAKE) -C stage1 update-stage0-commit DEPENDS stage1)
|
||||
|
||||
add_custom_target(test COMMAND $(MAKE) -C stage1 test DEPENDS stage1)
|
||||
|
||||
add_custom_target(
|
||||
bench
|
||||
COMMAND $(MAKE) -C stage2
|
||||
COMMAND $(MAKE) -C stage2 -j1 bench
|
||||
DEPENDS stage2
|
||||
)
|
||||
add_custom_target(
|
||||
bench-part1
|
||||
COMMAND $(MAKE) -C stage2
|
||||
COMMAND $(MAKE) -C stage2 -j1 bench-part1
|
||||
DEPENDS stage2
|
||||
)
|
||||
add_custom_target(
|
||||
bench-part2
|
||||
COMMAND $(MAKE) -C stage2
|
||||
COMMAND $(MAKE) -C stage2 -j1 bench-part2
|
||||
DEPENDS stage2
|
||||
)
|
||||
|
||||
add_custom_target(clean-stdlib COMMAND $(MAKE) -C stage1 clean-stdlib DEPENDS stage1)
|
||||
add_custom_target(test
|
||||
COMMAND $(MAKE) -C stage1 test
|
||||
DEPENDS stage1)
|
||||
|
||||
install(CODE "execute_process(COMMAND make -C stage1 install)")
|
||||
|
||||
add_custom_target(check-stage3 COMMAND diff "stage2/bin/lean" "stage3/bin/lean" DEPENDS stage3)
|
||||
add_custom_target(check-stage3
|
||||
COMMAND diff "stage2/bin/lean" "stage3/bin/lean"
|
||||
DEPENDS stage3)
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
{
|
||||
"version": 2,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 10,
|
||||
"patch": 0
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "release",
|
||||
"displayName": "Default development optimized build config",
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build/release"
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"displayName": "Debug build config",
|
||||
"cacheVariables": {
|
||||
"LEAN_EXTRA_CXX_FLAGS": "-DLEAN_DEFAULT_THREAD_STACK_SIZE=16*1024*1024",
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
},
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build/debug"
|
||||
},
|
||||
{
|
||||
"name": "reldebug",
|
||||
"displayName": "Release with assertions enabled",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithAssert"
|
||||
},
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build/reldebug"
|
||||
},
|
||||
{
|
||||
"name": "sanitize",
|
||||
"displayName": "Sanitize build config",
|
||||
"cacheVariables": {
|
||||
"LEAN_EXTRA_CXX_FLAGS": "-fsanitize=address,undefined -DLEAN_DEFAULT_THREAD_STACK_SIZE=16*1024*1024",
|
||||
"LEANC_EXTRA_CC_FLAGS": "-fsanitize=address,undefined",
|
||||
"LEAN_EXTRA_LINKER_FLAGS": "-fsanitize=address,undefined -fsanitize-link-c++-runtime",
|
||||
"SMALL_ALLOCATOR": "OFF",
|
||||
"USE_MIMALLOC": "OFF",
|
||||
"BSYMBOLIC": "OFF",
|
||||
"LEAN_TEST_VARS": "MAIN_STACK_SIZE=16000 TEST_STACK_SIZE=16000 LSAN_OPTIONS=max_leaks=10"
|
||||
},
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build/sanitize"
|
||||
},
|
||||
{
|
||||
"name": "sandebug",
|
||||
"inherits": ["sanitize", "debug"],
|
||||
"displayName": "Sanitize+debug build config",
|
||||
"binaryDir": "${sourceDir}/build/sandebug"
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "release",
|
||||
"configurePreset": "release"
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"configurePreset": "debug"
|
||||
},
|
||||
{
|
||||
"name": "reldebug",
|
||||
"configurePreset": "reldebug"
|
||||
},
|
||||
{
|
||||
"name": "sanitize",
|
||||
"configurePreset": "sanitize"
|
||||
},
|
||||
{
|
||||
"name": "sandebug",
|
||||
"configurePreset": "sandebug"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "release",
|
||||
"configurePreset": "release",
|
||||
"output": {"outputOnFailure": true, "shortProgress": true}
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"configurePreset": "debug",
|
||||
"inherits": "release"
|
||||
},
|
||||
{
|
||||
"name": "reldebug",
|
||||
"configurePreset": "reldebug",
|
||||
"inherits": "release"
|
||||
},
|
||||
{
|
||||
"name": "sanitize",
|
||||
"configurePreset": "sanitize",
|
||||
"inherits": "release"
|
||||
},
|
||||
{
|
||||
"name": "sandebug",
|
||||
"configurePreset": "sandebug",
|
||||
"inherits": "release"
|
||||
}
|
||||
]
|
||||
}
|
||||
52
CODEOWNERS
52
CODEOWNERS
@@ -4,51 +4,19 @@
|
||||
# Listed persons will automatically be asked by GitHub to review a PR touching these paths.
|
||||
# If multiple names are listed, a review by any of them is considered sufficient by default.
|
||||
|
||||
/.github/ @kim-em
|
||||
/RELEASES.md @kim-em
|
||||
/.github/ @Kha @semorrison
|
||||
/RELEASES.md @semorrison
|
||||
/src/ @leodemoura @Kha
|
||||
/src/Init/IO.lean @joehendrix
|
||||
/src/kernel/ @leodemoura
|
||||
/src/library/compiler/ @hargoniX
|
||||
/src/lake/ @tydeu
|
||||
/src/Lean/Compiler/ @leodemoura @hargoniX
|
||||
/src/Lean/Compiler/ @leodemoura
|
||||
/src/Lean/Data/Lsp/ @mhuisi
|
||||
/src/Lean/Elab/Deriving/ @kim-em
|
||||
/src/Lean/Elab/Tactic/ @kim-em
|
||||
/src/Lean/Language/ @Kha
|
||||
/src/Lean/Elab/Deriving/ @semorrison
|
||||
/src/Lean/Elab/Tactic/ @semorrison
|
||||
/src/Lean/Meta/Tactic/ @leodemoura
|
||||
/src/Lean/PrettyPrinter/ @kmill
|
||||
/src/Lean/Parser/ @Kha
|
||||
/src/Lean/PrettyPrinter/ @Kha
|
||||
/src/Lean/Server/ @mhuisi
|
||||
/src/Lean/Widget/ @Vtec234
|
||||
/src/Init/Data/ @kim-em
|
||||
/src/Init/Data/Array/Lemmas.lean @digama0
|
||||
/src/Init/Data/List/Lemmas.lean @digama0
|
||||
/src/Init/Data/List/BasicAux.lean @digama0
|
||||
/src/Init/Data/Array/Subarray.lean @david-christiansen
|
||||
/src/Lean/Elab/Tactic/RCases.lean @digama0
|
||||
/src/Init/RCases.lean @digama0
|
||||
/src/Lean/Elab/Tactic/Ext.lean @digama0
|
||||
/src/Init/Ext.lean @digama0
|
||||
/src/Lean/Elab/Tactic/Simpa.lean @digama0
|
||||
/src/Lean/Elab/Tactic/NormCast.lean @digama0
|
||||
/src/Lean/Meta/Tactic/NormCast.lean @digama0
|
||||
/src/Lean/Meta/Tactic/TryThis.lean @digama0
|
||||
/src/Lean/Elab/Tactic/SimpTrace.lean @digama0
|
||||
/src/Lean/Elab/Tactic/NoMatch.lean @digama0
|
||||
/src/Lean/Elab/Tactic/ShowTerm.lean @digama0
|
||||
/src/Lean/Elab/Tactic/Repeat.lean @digama0
|
||||
/src/Lean/Meta/Tactic/Repeat.lean @digama0
|
||||
/src/Lean/Meta/CoeAttr.lean @digama0
|
||||
/src/Lean/Elab/GuardMsgs.lean @digama0
|
||||
/src/Lean/Elab/Tactic/Guard.lean @digama0
|
||||
/src/Init/Guard.lean @digama0
|
||||
/src/Lean/Server/CodeActions/ @digama0
|
||||
/src/Std/ @TwoFX
|
||||
/src/Std/Tactic/BVDecide/ @hargoniX
|
||||
/src/Lean/Elab/Tactic/BVDecide/ @hargoniX
|
||||
/src/Std/Sat/ @hargoniX
|
||||
/src/Std/Do @sgraf812
|
||||
/src/Std/Tactic/Do @sgraf812
|
||||
/src/Lean/Elab/Tactic/Do @sgraf812
|
||||
/src/Init/Data/Range/Polymorphic @datokrat
|
||||
/src/Init/Data/Slice @datokrat
|
||||
/src/Init/Data/Iterators @datokrat
|
||||
/src/Std/Data/Iterators @datokrat
|
||||
/src/runtime/io.cpp @joehendrix
|
||||
|
||||
@@ -7,7 +7,7 @@ Helpful links
|
||||
-------
|
||||
|
||||
* [Development Setup](./doc/dev/index.md)
|
||||
* [Testing](./tests/README.md)
|
||||
* [Testing](./doc/dev/testing.md)
|
||||
* [Commit convention](./doc/dev/commit_convention.md)
|
||||
|
||||
Before You Submit a Pull Request (PR):
|
||||
@@ -63,20 +63,6 @@ Because the change will be squashed, there is no need to polish the commit messa
|
||||
Reviews and Feedback:
|
||||
----
|
||||
|
||||
The lean4 repo is managed by the Lean FRO's *triage team* that aims to provide initial feedback on new bug reports, PRs, and RFCs weekly.
|
||||
This feedback generally consists of prioritizing the ticket using one of the following categories:
|
||||
* label `P-high`: We will work on this issue
|
||||
* label `P-medium`: We may work on this issue if we find the time
|
||||
* label `P-low`: We are not planning to work on this issue
|
||||
* *closed*: This issue is already fixed, it is not an issue, or is not sufficiently compatible with our roadmap for the project and we will not work on it nor accept external contributions on it
|
||||
|
||||
For *bug reports*, the listed priority reflects our commitment to fixing the issue.
|
||||
It is generally indicative but not necessarily identical to the priority an external contribution addressing this bug would receive.
|
||||
For *PRs* and *RFCs*, the priority reflects our commitment to reviewing them and getting them to an acceptable state.
|
||||
Accepted RFCs are marked with the label `RFC accepted` and afterwards assigned a new "implementation" priority as with bug reports.
|
||||
|
||||
General guidelines for interacting with reviews and feedback:
|
||||
|
||||
**Be Patient**: Given the limited number of full-time maintainers and the volume of PRs, reviews may take some time.
|
||||
|
||||
**Engage Constructively**: Always approach feedback positively and constructively. Remember, reviews are about ensuring the best quality for the project, not personal criticism.
|
||||
|
||||
234
LICENSES
234
LICENSES
@@ -1341,237 +1341,3 @@ whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
==============================================================================
|
||||
CaDiCaL is under the MIT License:
|
||||
==============================================================================
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2021 Armin Biere, Johannes Kepler University Linz, Austria
|
||||
Copyright (c) 2020-2021 Mathias Fleury, Johannes Kepler University Linz, Austria
|
||||
Copyright (c) 2020-2021 Nils Froleyks, Johannes Kepler University Linz, Austria
|
||||
Copyright (c) 2022-2024 Katalin Fazekas, Vienna University of Technology, Austria
|
||||
Copyright (c) 2021-2024 Armin Biere, University of Freiburg, Germany
|
||||
Copyright (c) 2021-2024 Mathias Fleury, University of Freiburg, Germany
|
||||
Copyright (c) 2023-2024 Florian Pollitt, University of Freiburg, Germany
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
==============================================================================
|
||||
leantar is by Mario Carneiro and distributed under the Apache 2.0 License:
|
||||
==============================================================================
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
12
README.md
12
README.md
@@ -2,19 +2,19 @@ This is the repository for **Lean 4**.
|
||||
|
||||
# About
|
||||
|
||||
- [Quickstart](https://lean-lang.org/install/)
|
||||
- [Quickstart](https://lean-lang.org/lean4/doc/quickstart.html)
|
||||
- [Homepage](https://lean-lang.org)
|
||||
- [Theorem Proving Tutorial](https://lean-lang.org/theorem_proving_in_lean4/)
|
||||
- [Functional Programming in Lean](https://lean-lang.org/functional_programming_in_lean/)
|
||||
- [Documentation Overview](https://lean-lang.org/learn/)
|
||||
- [Language Reference](https://lean-lang.org/doc/reference/latest/)
|
||||
- [Manual](https://lean-lang.org/lean4/doc/)
|
||||
- [Release notes](RELEASES.md) starting at v4.0.0-m3
|
||||
- [Examples](https://lean-lang.org/examples/)
|
||||
- [Examples](https://lean-lang.org/lean4/doc/examples.html)
|
||||
- [External Contribution Guidelines](CONTRIBUTING.md)
|
||||
- [FAQ](https://lean-lang.org/lean4/doc/faq.html)
|
||||
|
||||
# Installation
|
||||
|
||||
See [Install Lean](https://lean-lang.org/install/).
|
||||
See [Setting Up Lean](https://lean-lang.org/lean4/doc/setup.html).
|
||||
|
||||
# Contributing
|
||||
|
||||
@@ -22,4 +22,4 @@ Please read our [Contribution Guidelines](CONTRIBUTING.md) first.
|
||||
|
||||
# Building from Source
|
||||
|
||||
See [Building Lean](doc/make/index.md).
|
||||
See [Building Lean](https://lean-lang.org/lean4/doc/make/index.html).
|
||||
|
||||
1571
RELEASES.md
1571
RELEASES.md
File diff suppressed because it is too large
Load Diff
9
default.nix
Normal file
9
default.nix
Normal file
@@ -0,0 +1,9 @@
|
||||
# used for `nix-shell https://github.com/leanprover/lean4/archive/master.tar.gz -A nix`
|
||||
{ nix = (import ./shell.nix {}).nix; } //
|
||||
(import (
|
||||
fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/c75e76f80c57784a6734356315b306140646ee84.tar.gz";
|
||||
sha256 = "071aal00zp2m9knnhddgr2wqzlx6i6qa1263lv1y7bdn2w20h10h"; }
|
||||
) {
|
||||
src = ./.;
|
||||
}).defaultNix
|
||||
1
doc/.gitignore
vendored
Normal file
1
doc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
out
|
||||
@@ -1,4 +1,4 @@
|
||||
open Batteries
|
||||
open Std
|
||||
open Lean
|
||||
|
||||
inductive BoolExpr where
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# Developer Documentation and Examples
|
||||
|
||||
This directory contains documentation that describes how to work on
|
||||
Lean itself, as well as examples that are included in documentation
|
||||
that's hosted on the Lean website. The `make` directory contains
|
||||
information on building Lean, and the `dev` directory describes how to
|
||||
work on Lean.
|
||||
|
||||
The [documentation section](https://lean-lang.org/documentation) has
|
||||
links to documentation that describes how to use Lean itself.
|
||||
93
doc/SUMMARY.md
Normal file
93
doc/SUMMARY.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Summary
|
||||
|
||||
- [What is Lean](./whatIsLean.md)
|
||||
- [Tour of Lean](./tour.md)
|
||||
- [Setting Up Lean](./quickstart.md)
|
||||
- [Extended Setup Notes](./setup.md)
|
||||
- [Theorem Proving in Lean](./tpil.md)
|
||||
- [Functional Programming in Lean](fplean.md)
|
||||
- [Examples](./examples.md)
|
||||
- [Palindromes](examples/palindromes.lean.md)
|
||||
- [Binary Search Trees](examples/bintree.lean.md)
|
||||
- [A Certified Type Checker](examples/tc.lean.md)
|
||||
- [The Well-Typed Interpreter](examples/interp.lean.md)
|
||||
- [Dependent de Bruijn Indices](examples/deBruijn.lean.md)
|
||||
- [Parametric Higher-Order Abstract Syntax](examples/phoas.lean.md)
|
||||
|
||||
# Language Manual
|
||||
<!-- - [Using Lean](./using_lean.md) -->
|
||||
<!-- - [Lexical Structure](./lexical_structure.md) -->
|
||||
<!-- - [Expressions](./expressions.md) -->
|
||||
<!-- - [Declarations](./declarations.md) -->
|
||||
- [Organizational features](./organization.md)
|
||||
- [Sections](./sections.md)
|
||||
- [Namespaces](./namespaces.md)
|
||||
- [Implicit Arguments](./implicit.md)
|
||||
- [Auto Bound Implicit Arguments](./autobound.md)
|
||||
<!-- - [Dependent Types](./deptypes.md) -->
|
||||
<!-- - [Simple Type Theory](./simptypes.md) -->
|
||||
<!-- - [Types as objects](./typeobjs.md) -->
|
||||
<!-- - [Function Abstraction and Evaluation](./funabst.md) -->
|
||||
<!-- - [Introducing Definitions](./introdef.md) -->
|
||||
<!-- - [What makes dependent type theory dependent?](./dep.md) -->
|
||||
<!-- - [Tactics](./tactics.md) -->
|
||||
- [Syntax Extensions](./syntax.md)
|
||||
- [The `do` Notation](./do.md)
|
||||
- [String Interpolation](./stringinterp.md)
|
||||
- [User-Defined Notation](./notation.md)
|
||||
- [Macro Overview](./macro_overview.md)
|
||||
- [Elaborators](./elaborators.md)
|
||||
- [Examples](./syntax_examples.md)
|
||||
- [Balanced Parentheses](./syntax_example.md)
|
||||
- [Arithmetic DSL](./metaprogramming-arith.md)
|
||||
- [Declaring New Types](./decltypes.md)
|
||||
- [Enumerated Types](./enum.md)
|
||||
- [Inductive Types](./inductive.md)
|
||||
- [Structures](./struct.md)
|
||||
- [Type classes](./typeclass.md)
|
||||
- [Unification Hints](./unifhint.md)
|
||||
- [Builtin Types](./builtintypes.md)
|
||||
- [Natural number](./nat.md)
|
||||
- [Integer](./int.md)
|
||||
- [Fixed precision unsigned integer](./uint.md)
|
||||
- [Float](./float.md)
|
||||
- [Array](./array.md)
|
||||
- [List](./list.md)
|
||||
- [Character](./char.md)
|
||||
- [String](./string.md)
|
||||
- [Option](./option.md)
|
||||
- [Thunk](./thunk.md)
|
||||
- [Task and Thread](./task.md)
|
||||
- [Functions](./functions.md)
|
||||
- [Monads](./monads/intro.md)
|
||||
- [Functor](./monads/functors.lean.md)
|
||||
- [Applicative](./monads/applicatives.lean.md)
|
||||
- [Monad](./monads/monads.lean.md)
|
||||
- [Reader](./monads/readers.lean.md)
|
||||
- [State](./monads/states.lean.md)
|
||||
- [Except](./monads/except.lean.md)
|
||||
- [Transformers](./monads/transformers.lean.md)
|
||||
- [Laws](./monads/laws.lean.md)
|
||||
|
||||
# Other
|
||||
|
||||
- [Frequently Asked Questions](./faq.md)
|
||||
- [Significant Changes from Lean 3](./lean3changes.md)
|
||||
- [Syntax Highlighting Lean in LaTeX](./syntax_highlight_in_latex.md)
|
||||
- [User Widgets](examples/widgets.lean.md)
|
||||
- [Semantic Highlighting](./semantic_highlighting.md)
|
||||
|
||||
# Development
|
||||
|
||||
- [Development Guide](./dev/index.md)
|
||||
- [Building Lean](./make/index.md)
|
||||
- [Ubuntu Setup](./make/ubuntu.md)
|
||||
- [macOS Setup](./make/osx-10.9.md)
|
||||
- [Windows MSYS2 Setup](./make/msys2.md)
|
||||
- [Windows with WSL](./make/wsl.md)
|
||||
- [Bootstrapping](./dev/bootstrap.md)
|
||||
- [Testing](./dev/testing.md)
|
||||
- [Debugging](./dev/debugging.md)
|
||||
- [Commit Convention](./dev/commit_convention.md)
|
||||
- [Building This Manual](./dev/mdbook.md)
|
||||
- [Foreign Function Interface](./dev/ffi.md)
|
||||
786
doc/alectryon.css
Normal file
786
doc/alectryon.css
Normal file
@@ -0,0 +1,786 @@
|
||||
@charset "UTF-8";
|
||||
/*
|
||||
Copyright © 2019 Clément Pit-Claudel
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/*******************************/
|
||||
/* CSS reset for .alectryon-io */
|
||||
/*******************************/
|
||||
|
||||
.content {
|
||||
/*
|
||||
Use `initial` instead of `contents` to avoid a browser bug which removes
|
||||
the element from the accessibility tree.
|
||||
https://developer.mozilla.org/en-US/docs/Web/CSS/display#display_contents
|
||||
*/
|
||||
display: initial;
|
||||
}
|
||||
|
||||
.alectryon-io blockquote {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.alectryon-io blockquote:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alectryon-io label {
|
||||
display: inline;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.alectryon-io a {
|
||||
text-decoration: none !important;
|
||||
font-style: oblique !important;
|
||||
color: unset;
|
||||
}
|
||||
|
||||
/* Undo <small> and <blockquote>, added to improve RSS rendering. */
|
||||
|
||||
.alectryon-io small.alectryon-output,
|
||||
.alectryon-io small.alectryon-type-info {
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.alectryon-io blockquote.alectryon-goal,
|
||||
.alectryon-io blockquote.alectryon-message {
|
||||
font-weight: normal;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
/***************/
|
||||
/* Main styles */
|
||||
/***************/
|
||||
|
||||
.alectryon-coqdoc .doc .code,
|
||||
.alectryon-coqdoc .doc .comment,
|
||||
.alectryon-coqdoc .doc .inlinecode,
|
||||
.alectryon-mref,
|
||||
.alectryon-block, .alectryon-io,
|
||||
.alectryon-toggle-label, .alectryon-banner {
|
||||
font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
|
||||
font-size: 0.875em;
|
||||
font-feature-settings: "COQX" 1 /* Coq ligatures */, "XV00" 1 /* Legacy */, "calt" 1 /* Fallback */;
|
||||
line-height: initial;
|
||||
}
|
||||
|
||||
.alectryon-io, .alectryon-block, .alectryon-toggle-label, .alectryon-banner {
|
||||
overflow: visible;
|
||||
overflow-wrap: break-word;
|
||||
position: relative;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/*
|
||||
CoqIDE doesn't turn off the unicode bidirectional algorithm (and PG simply
|
||||
respects the user's `bidi-display-reordering` setting), so don't turn it off
|
||||
here either. But beware unexpected results like `Definition test_אב := 0.`
|
||||
|
||||
.alectryon-io span {
|
||||
direction: ltr;
|
||||
unicode-bidi: bidi-override;
|
||||
}
|
||||
|
||||
In any case, make an exception for comments:
|
||||
|
||||
.highlight .c {
|
||||
direction: embed;
|
||||
unicode-bidi: initial;
|
||||
}
|
||||
*/
|
||||
|
||||
.alectryon-mref,
|
||||
.alectryon-mref-marker {
|
||||
align-self: center;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
font-size: 80%;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
box-shadow: 0 0 0 1pt black;
|
||||
padding: 1pt 0.3em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.alectryon-block .alectryon-mref-marker,
|
||||
.alectryon-io .alectryon-mref-marker {
|
||||
user-select: none;
|
||||
margin: -0.25em 0 -0.25em 0.5em;
|
||||
}
|
||||
|
||||
.alectryon-inline .alectryon-mref-marker {
|
||||
margin: -0.25em 0.15em -0.25em 0.625em; /* 625 = 0.5em / 80% */
|
||||
}
|
||||
|
||||
.alectryon-mref {
|
||||
color: inherit;
|
||||
margin: -0.5em 0.25em;
|
||||
}
|
||||
|
||||
.alectryon-goal:target .goal-separator .alectryon-mref-marker,
|
||||
:target > .alectryon-mref-marker {
|
||||
animation: blink 0.2s step-start 0s 3 normal none;
|
||||
background-color: #fcaf3e;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
box-shadow: 0 0 0 3pt #fcaf3e, 0 0 0 4pt black;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.alectryon-toggle,
|
||||
.alectryon-io .alectryon-extra-goal-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alectryon-bubble,
|
||||
.alectryon-io label,
|
||||
.alectryon-toggle-label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.alectryon-toggle-label {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-input {
|
||||
padding: 0.1em 0; /* Enlarge the hitbox slightly to fill interline gaps */
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-token {
|
||||
white-space: pre-wrap;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-input {
|
||||
/* FIXME if keywords were ‘bolder’ we wouldn't need !important */
|
||||
font-weight: bold !important; /* Use !important to avoid a * selector */
|
||||
}
|
||||
|
||||
.alectryon-bubble:before,
|
||||
.alectryon-toggle-label:before,
|
||||
.alectryon-io label.alectryon-input:after,
|
||||
.alectryon-io .alectryon-goal > label:before {
|
||||
border: 1px solid #babdb6;
|
||||
border-radius: 1em;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
height: 0.25em;
|
||||
margin-bottom: 0.15em;
|
||||
vertical-align: middle;
|
||||
width: 0.75em;
|
||||
}
|
||||
|
||||
.alectryon-toggle-label:before,
|
||||
.alectryon-io .alectryon-goal > label:before {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-goal > label:before {
|
||||
margin-top: 0.125em;
|
||||
}
|
||||
|
||||
.alectryon-io label.alectryon-input {
|
||||
padding-right: 1em; /* Prevent line wraps before the checkbox bubble */
|
||||
}
|
||||
|
||||
.alectryon-io label.alectryon-input:after {
|
||||
margin-left: 0.25em;
|
||||
margin-right: -1em; /* Compensate for the anti-wrapping space */
|
||||
}
|
||||
|
||||
.alectryon-failed {
|
||||
/* Underlines are broken in Chrome (they reset at each element boundary)… */
|
||||
/* text-decoration: red wavy underline; */
|
||||
/* … but it isn't too noticeable with dots */
|
||||
text-decoration: red dotted underline;
|
||||
text-decoration-skip-ink: none;
|
||||
/* Chrome prints background images in low resolution, yielding a blurry underline */
|
||||
/* background: bottom / 0.3em auto repeat-x url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyLjY0NiAxLjg1MiIgaGVpZ2h0PSI4IiB3aWR0aD0iMTAiPjxwYXRoIGQ9Ik0wIC4yNjVjLjc5NCAwIC41MyAxLjMyMiAxLjMyMyAxLjMyMi43OTQgMCAuNTMtMS4zMjIgMS4zMjMtMS4zMjIiIGZpbGw9Im5vbmUiIHN0cm9rZT0icmVkIiBzdHJva2Utd2lkdGg9Ii41MjkiLz48L3N2Zz4=); */
|
||||
}
|
||||
|
||||
/* Wrapping :hover rules in a media query ensures that tapping a Coq sentence
|
||||
doesn't trigger its :hover state (otherwise, on mobile, tapping a sentence to
|
||||
hide its output causes it to remain visible (its :hover state gets triggered.
|
||||
We only do it for the default style though, since other styles don't put the
|
||||
output over the main text, so showing too much is not an issue. */
|
||||
@media (any-hover: hover) {
|
||||
.alectryon-bubble:hover:before,
|
||||
.alectryon-toggle-label:hover:before,
|
||||
.alectryon-io label.alectryon-input:hover:after {
|
||||
background: #eeeeec;
|
||||
}
|
||||
|
||||
.alectryon-io label.alectryon-input:hover {
|
||||
text-decoration: underline dotted #babdb6;
|
||||
text-shadow: 0 0 1px rgb(46, 52, 54, 0.3); /* #2e3436 + opacity */
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-sentence:hover .alectryon-output,
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper,
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper {
|
||||
z-index: 2; /* Place hovered goals above .alectryon-sentence.alectryon-target ones */
|
||||
}
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + .alectryon-toggle-label:before,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked + label.alectryon-input:after,
|
||||
.alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal > label:before {
|
||||
background-color: #babdb6;
|
||||
border-color: #babdb6;
|
||||
}
|
||||
|
||||
/* Disable clicks on sentences when the document-wide toggle is set. */
|
||||
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input {
|
||||
cursor: unset;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Hide individual checkboxes when the document-wide toggle is set. */
|
||||
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* .alectryon-output is displayed by toggles, :hover, and .alectryon-target rules */
|
||||
.alectryon-io .alectryon-output {
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
padding: 0.25em 0;
|
||||
overflow: visible; /* Let box-shadows overflow */
|
||||
z-index: 1; /* Default to an index lower than that used by :hover */
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info-wrapper {
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info-wrapper.full-width {
|
||||
left: 0;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info .goal-separator {
|
||||
height: unset;
|
||||
margin-top: 0em;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info-wrapper .alectryon-type-info {
|
||||
box-sizing: border-box;
|
||||
bottom: 100%;
|
||||
position: absolute;
|
||||
/*padding: 0.25em 0;*/
|
||||
visibility: hidden;
|
||||
overflow: visible; /* Let box-shadows overflow */
|
||||
z-index: 1; /* Default to an index lower than that used by :hover */
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-type-info-wrapper .alectryon-type-info .alectryon-goal.alectryon-docstring {
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
@media (any-hover: hover) { /* See note above about this @media query */
|
||||
.alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alectryon-io.output-hidden .alectryon-sentence:hover .alectryon-output:not(:hover) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.alectryon-io.type-info-hidden .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info,
|
||||
.alectryon-io.type-info-hidden .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info {
|
||||
/*visibility: hidden !important;*/
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info,
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info {
|
||||
visibility: visible;
|
||||
transition-delay: 0.5s;
|
||||
}
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-output {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Indicate active (hovered or targeted) goals with a shadow. */
|
||||
.alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-messages,
|
||||
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-messages,
|
||||
.alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-goals,
|
||||
.alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-goals,
|
||||
.alectryon-io .alectryon-token:hover .alectryon-type-info-wrapper .alectryon-type-info {
|
||||
box-shadow: 2px 2px 2px gray;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-extra-goals .alectryon-goal .goal-hyps {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-extra-goals .alectryon-extra-goal-toggle:not(:checked) + .alectryon-goal label.goal-separator hr {
|
||||
/* Dashes indicate that the hypotheses are hidden */
|
||||
border-top-style: dashed;
|
||||
}
|
||||
|
||||
|
||||
/* Show just a small preview of the other goals; this is undone by the
|
||||
"extra-goal" toggle and by :hover and .alectryon-target in windowed mode. */
|
||||
.alectryon-io .alectryon-extra-goals .alectryon-goal .goal-conclusion {
|
||||
max-height: 5.2em;
|
||||
overflow-y: auto;
|
||||
/* Combining ‘overflow-y: auto’ with ‘display: inline-block’ causes extra space
|
||||
to be added below the box. ‘vertical-align: middle’ gets rid of it. */
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-goals,
|
||||
.alectryon-io .alectryon-messages {
|
||||
background: #f6f7f6;
|
||||
/*border: thin solid #d3d7cf; /* Convenient when pre's background is already #EEE */
|
||||
display: block;
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
.alectryon-message::before {
|
||||
content: '';
|
||||
float: right;
|
||||
/* etc/svg/square-bubble-xl.svg */
|
||||
background: url("data:image/svg+xml,%3Csvg width='14' height='14' viewBox='0 0 3.704 3.704' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill-rule='evenodd' stroke='%23000' stroke-width='.264'%3E%3Cpath d='M.794.934h2.115M.794 1.463h1.455M.794 1.992h1.852'/%3E%3C/g%3E%3Cpath d='M.132.14v2.646h.794v.661l.926-.661h1.72V.14z' fill='none' stroke='%23000' stroke-width='.265'/%3E%3C/svg%3E") top right no-repeat;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + label + .alectryon-container {
|
||||
width: unset;
|
||||
}
|
||||
|
||||
/* Show goals when a toggle is set */
|
||||
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input + .alectryon-output,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output {
|
||||
display: block;
|
||||
position: static;
|
||||
width: unset;
|
||||
background: unset; /* Override the backgrounds set in floating in windowed mode */
|
||||
padding: 0.25em 0; /* Re-assert so that later :hover rules don't override this padding */
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + label + .alectryon-container label.alectryon-input + .alectryon-output .goal-hyps,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output .goal-hyps {
|
||||
/* Overridden back in windowed style */
|
||||
flex-flow: row wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + label + .alectryon-container .alectryon-sentence .alectryon-output > div,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output > div {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal .goal-hyps {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal .goal-conclusion {
|
||||
max-height: unset;
|
||||
overflow-y: unset;
|
||||
}
|
||||
|
||||
.alectryon-toggle:checked + label + .alectryon-container .alectryon-sentence > .alectryon-toggle ~ .alectryon-wsp,
|
||||
.alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-wsp {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-messages,
|
||||
.alectryon-io .alectryon-message,
|
||||
.alectryon-io .alectryon-goals,
|
||||
.alectryon-io .alectryon-goal,
|
||||
.alectryon-io .goal-hyps > span,
|
||||
.alectryon-io .goal-conclusion {
|
||||
border-radius: 0.15em;
|
||||
}
|
||||
|
||||
.alectryon-io .alectryon-goal,
|
||||
.alectryon-io .alectryon-message {
|
||||
align-items: center;
|
||||
background: #f6f7f6;
|
||||
border: 0em;
|
||||
display: block;
|
||||
flex-direction: column;
|
||||
margin: 0.25em;
|
||||
padding: 0.5em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps {
|
||||
align-content: space-around;
|
||||
align-items: baseline;
|
||||
display: flex;
|
||||
flex-flow: column nowrap; /* re-stated in windowed mode */
|
||||
justify-content: space-around;
|
||||
/* LATER use a ‘gap’ property instead of margins once supported */
|
||||
margin: -0.15em -0.25em; /* -0.15em to cancel the item spacing */
|
||||
padding-bottom: 0.35em; /* 0.5em-0.15em to cancel the 0.5em of .goal-separator */
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > br {
|
||||
display: none; /* Only for RSS readers */
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > span,
|
||||
.alectryon-io .goal-conclusion {
|
||||
/*background: #eeeeec;*/
|
||||
display: inline-block;
|
||||
padding: 0.15em 0.35em;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > span {
|
||||
align-items: baseline;
|
||||
display: inline-flex;
|
||||
margin: 0.15em 0.25em;
|
||||
}
|
||||
|
||||
.alectryon-block var,
|
||||
.alectryon-inline var,
|
||||
.alectryon-io .goal-hyps > span > var {
|
||||
font-weight: 600;
|
||||
font-style: unset;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > span > var {
|
||||
/* Shrink the list of names, but let it grow as long as space is available. */
|
||||
flex-basis: min-content;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-hyps > span b {
|
||||
font-weight: 600;
|
||||
margin: 0 0 0 0.5em;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.alectryon-io .hyp-body,
|
||||
.alectryon-io .hyp-type {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-separator {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 1em; /* Fixed height to ignore goal name and markers */
|
||||
margin-top: -0.5em; /* Compensated in .goal-hyps when shown */
|
||||
}
|
||||
|
||||
.alectryon-io .goal-separator hr {
|
||||
border: none;
|
||||
border-top: thin solid #555753;
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.alectryon-io .goal-separator .goal-name {
|
||||
font-size: 0.75em;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
/**********/
|
||||
/* Banner */
|
||||
/**********/
|
||||
|
||||
.alectryon-banner {
|
||||
background: #eeeeec;
|
||||
border: 1px solid #babcbd;
|
||||
font-size: 0.75em;
|
||||
padding: 0.25em;
|
||||
text-align: center;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.alectryon-banner a {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.alectryon-banner kbd {
|
||||
background: #d3d7cf;
|
||||
border-radius: 0.15em;
|
||||
border: 1px solid #babdb6;
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
font-family: inherit;
|
||||
font-size: 0.9em;
|
||||
height: 1.3em;
|
||||
line-height: 1.2em;
|
||||
margin: -0.25em 0;
|
||||
padding: 0 0.25em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/**********/
|
||||
/* Toggle */
|
||||
/**********/
|
||||
|
||||
.alectryon-toggle-label {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
/******************/
|
||||
/* Floating style */
|
||||
/******************/
|
||||
|
||||
/* If there's space, display goals to the right of the code, not below it. */
|
||||
@media (min-width: 80rem) {
|
||||
/* Unlike the windowed case, we don't want to move output blocks to the side
|
||||
when they are both :checked and -targeted, since it gets confusing as
|
||||
things jump around; hence the commented-output part of the selector,
|
||||
which would otherwise increase specificity */
|
||||
.alectryon-floating .alectryon-sentence.alectryon-target /* > .alectryon-toggle ~ */ .alectryon-output,
|
||||
.alectryon-floating .alectryon-sentence:hover .alectryon-output {
|
||||
top: 0;
|
||||
left: 100%;
|
||||
right: -100%;
|
||||
padding: 0 0.5em;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.alectryon-floating .alectryon-output {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.alectryon-floating .alectryon-sentence:hover .alectryon-output {
|
||||
background: white; /* Ensure that short goals hide long ones */
|
||||
}
|
||||
|
||||
/* This odd margin-bottom property prevents the sticky div from bumping
|
||||
against the bottom of its container (.alectryon-output). The alternative
|
||||
would be enlarging .alectryon-output, but that would cause overflows,
|
||||
enlarging scrollbars and yielding scrolling towards the bottom of the
|
||||
page. Doing things this way instead makes it possible to restrict
|
||||
.alectryon-output to a reasonable size (100%, through top = bottom = 0).
|
||||
See also https://stackoverflow.com/questions/43909940/. */
|
||||
/* See note on specificity above */
|
||||
.alectryon-floating .alectryon-sentence.alectryon-target /* > .alectryon-toggle ~ */ .alectryon-output > div,
|
||||
.alectryon-floating .alectryon-sentence:hover .alectryon-output > div {
|
||||
margin-bottom: -200%;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.alectryon-floating .alectryon-toggle:checked + label + .alectryon-container .alectryon-sentence .alectryon-output > div,
|
||||
.alectryon-floating .alectryon-io .alectryon-sentence > .alectryon-toggle:checked ~ .alectryon-output > div {
|
||||
margin-bottom: unset; /* Undo the margin */
|
||||
}
|
||||
|
||||
/* Float underneath the current fragment
|
||||
@media (max-width: 80rem) {
|
||||
.alectryon-floating .alectryon-output {
|
||||
top: 100%;
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
/********************/
|
||||
/* Multi-pane style */
|
||||
/********************/
|
||||
|
||||
.alectryon-windowed {
|
||||
border: 0 solid #2e3436;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-sentence:hover .alectryon-output {
|
||||
background: white; /* Ensure that short goals hide long ones */
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-output {
|
||||
position: fixed; /* Overwritten by the ‘:checked’ rules */
|
||||
}
|
||||
|
||||
/* See note about specificity below */
|
||||
.alectryon-windowed .alectryon-sentence:hover .alectryon-output,
|
||||
.alectryon-windowed .alectryon-sentence.alectryon-target > .alectryon-toggle ~ .alectryon-output {
|
||||
padding: 0.5em;
|
||||
overflow-y: auto; /* Windowed contents may need to scroll */
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-messages,
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-messages,
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence:hover .alectryon-output:not(:hover) .alectryon-goals,
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .alectryon-goals {
|
||||
box-shadow: none; /* A shadow is unnecessary here and incompatible with overflow-y set to auto */
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-io .alectryon-sentence.alectryon-target .alectryon-output .goal-hyps {
|
||||
/* Restated to override the :checked style */
|
||||
flex-flow: column nowrap;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
|
||||
.alectryon-windowed .alectryon-sentence.alectryon-target .alectryon-extra-goals .alectryon-goal .goal-conclusion
|
||||
/* Like .alectryon-io .alectryon-extra-goal-toggle:checked + .alectryon-goal .goal-conclusion */ {
|
||||
max-height: unset;
|
||||
overflow-y: unset;
|
||||
}
|
||||
|
||||
.alectryon-windowed .alectryon-output > div {
|
||||
display: flex; /* Put messages after goals */
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
/*********************/
|
||||
/* Standalone styles */
|
||||
/*********************/
|
||||
|
||||
.alectryon-standalone {
|
||||
font-family: 'IBM Plex Serif', 'PT Serif', 'Merriweather', 'DejaVu Serif', serif;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 50rem) {
|
||||
html.alectryon-standalone {
|
||||
/* Prevent flickering when hovering a block causes scrollbars to appear. */
|
||||
margin-left: calc(100vw - 100%);
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Coqdoc */
|
||||
|
||||
.alectryon-coqdoc .doc .code,
|
||||
.alectryon-coqdoc .doc .inlinecode,
|
||||
.alectryon-coqdoc .doc .comment {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.alectryon-coqdoc .doc .comment {
|
||||
color: #eeeeec;
|
||||
}
|
||||
|
||||
.alectryon-coqdoc .doc .paragraph {
|
||||
height: 0.75em;
|
||||
}
|
||||
|
||||
/* Centered, Floating */
|
||||
|
||||
.alectryon-standalone .alectryon-centered,
|
||||
.alectryon-standalone .alectryon-floating {
|
||||
max-width: 50rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 80rem) {
|
||||
.alectryon-standalone .alectryon-floating {
|
||||
max-width: 80rem;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-floating > * {
|
||||
width: 50%;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Windowed */
|
||||
|
||||
.alectryon-standalone .alectryon-windowed {
|
||||
display: block;
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-windowed > * {
|
||||
/* Override properties of docutils_basic.css */
|
||||
margin-left: 0;
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-io {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* No need to predicate the ‘:hover’ rules below on ‘:not(:checked)’, since ‘left’,
|
||||
‘right’, ‘top’, and ‘bottom’ will be inactived by the :checked rules setting
|
||||
‘position’ to ‘static’ */
|
||||
|
||||
|
||||
/* Specificity: We want the output to stay inline when hovered while unfolded
|
||||
(:checked), but we want it to move when it's targeted (i.e. when the user
|
||||
is browsing goals one by one using the keyboard, in which case we want to
|
||||
goals to appear in consistent locations). The selectors below ensure
|
||||
that :hover < :checked < -targeted in terms of specificity. */
|
||||
/* LATER: Reimplement this stuff with CSS variables */
|
||||
.alectryon-windowed .alectryon-sentence.alectryon-target > .alectryon-toggle ~ .alectryon-output {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 60rem) {
|
||||
.alectryon-standalone .alectryon-windowed {
|
||||
border-right-width: thin;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 50%;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-sentence:hover .alectryon-output,
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-sentence.alectryon-target .alectryon-output {
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 60rem) {
|
||||
.alectryon-standalone .alectryon-windowed {
|
||||
border-bottom-width: 1px;
|
||||
bottom: 40%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-sentence:hover .alectryon-output,
|
||||
.alectryon-standalone .alectryon-windowed .alectryon-sentence.alectryon-target .alectryon-output {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 60%;
|
||||
}
|
||||
}
|
||||
190
doc/alectryon.js
Normal file
190
doc/alectryon.js
Normal file
@@ -0,0 +1,190 @@
|
||||
var Alectryon;
|
||||
(function(Alectryon) {
|
||||
(function (slideshow) {
|
||||
function anchor(sentence) { return "#" + sentence.id; }
|
||||
|
||||
function current_sentence() { return slideshow.sentences[slideshow.pos]; }
|
||||
|
||||
function unhighlight() {
|
||||
var sentence = current_sentence();
|
||||
if (sentence) sentence.classList.remove("alectryon-target");
|
||||
slideshow.pos = -1;
|
||||
}
|
||||
|
||||
function highlight(sentence) {
|
||||
sentence.classList.add("alectryon-target");
|
||||
}
|
||||
|
||||
function scroll(sentence) {
|
||||
// Put the top of the current fragment close to the top of the
|
||||
// screen, but scroll it out of view if showing it requires pushing
|
||||
// the sentence past half of the screen. If sentence is already in
|
||||
// a reasonable position, don't move.
|
||||
var parent = sentence.parentElement;
|
||||
/* We want to scroll the whole document, so start at root… */
|
||||
while (parent && !parent.classList.contains("alectryon-root"))
|
||||
parent = parent.parentElement;
|
||||
/* … and work up from there to find a scrollable element.
|
||||
parent.scrollHeight can be greater than parent.clientHeight
|
||||
without showing scrollbars, so we add a 10px buffer. */
|
||||
while (parent && parent.scrollHeight <= parent.clientHeight + 10)
|
||||
parent = parent.parentElement;
|
||||
/* <body> and <html> elements can have their client rect overflow
|
||||
* the window if their height is unset, so scroll the window
|
||||
* instead */
|
||||
if (parent && (parent.nodeName == "BODY" || parent.nodeName == "HTML"))
|
||||
parent = null;
|
||||
|
||||
var rect = function(e) { return e.getBoundingClientRect(); };
|
||||
var parent_box = parent ? rect(parent) : { y: 0, height: window.innerHeight },
|
||||
sentence_y = rect(sentence).y - parent_box.y,
|
||||
fragment_y = rect(sentence.parentElement).y - parent_box.y;
|
||||
|
||||
// The assertion below sometimes fails for the first element in a block.
|
||||
// console.assert(sentence_y >= fragment_y);
|
||||
|
||||
if (sentence_y < 0.1 * parent_box.height ||
|
||||
sentence_y > 0.7 * parent_box.height) {
|
||||
(parent || window).scrollBy(
|
||||
0, Math.max(sentence_y - 0.5 * parent_box.height,
|
||||
fragment_y - 0.1 * parent_box.height));
|
||||
}
|
||||
}
|
||||
|
||||
function highlighted(pos) {
|
||||
return slideshow.pos == pos;
|
||||
}
|
||||
|
||||
function navigate(pos, inhibitScroll) {
|
||||
unhighlight();
|
||||
slideshow.pos = Math.min(Math.max(pos, 0), slideshow.sentences.length - 1);
|
||||
var sentence = current_sentence();
|
||||
highlight(sentence);
|
||||
if (!inhibitScroll)
|
||||
scroll(sentence);
|
||||
}
|
||||
|
||||
var keys = {
|
||||
PAGE_UP: 33,
|
||||
PAGE_DOWN: 34,
|
||||
ARROW_UP: 38,
|
||||
ARROW_DOWN: 40,
|
||||
h: 72, l: 76, p: 80, n: 78
|
||||
};
|
||||
|
||||
function onkeydown(e) {
|
||||
e = e || window.event;
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
if (e.keyCode == keys.ARROW_UP)
|
||||
slideshow.previous();
|
||||
else if (e.keyCode == keys.ARROW_DOWN)
|
||||
slideshow.next();
|
||||
else
|
||||
return;
|
||||
} else {
|
||||
// if (e.keyCode == keys.PAGE_UP || e.keyCode == keys.p || e.keyCode == keys.h)
|
||||
// slideshow.previous();
|
||||
// else if (e.keyCode == keys.PAGE_DOWN || e.keyCode == keys.n || e.keyCode == keys.l)
|
||||
// slideshow.next();
|
||||
// else
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function start() {
|
||||
slideshow.navigate(0);
|
||||
}
|
||||
|
||||
function toggleHighlight(idx) {
|
||||
if (highlighted(idx))
|
||||
unhighlight();
|
||||
else
|
||||
navigate(idx, true);
|
||||
}
|
||||
|
||||
function handleClick(evt) {
|
||||
if (evt.ctrlKey || evt.metaKey) {
|
||||
var sentence = evt.currentTarget;
|
||||
|
||||
// Ensure that the goal is shown on the side, not inline
|
||||
var checkbox = sentence.getElementsByClassName("alectryon-toggle")[0];
|
||||
if (checkbox)
|
||||
checkbox.checked = false;
|
||||
|
||||
toggleHighlight(sentence.alectryon_index);
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.onkeydown = onkeydown;
|
||||
slideshow.pos = -1;
|
||||
slideshow.sentences = Array.from(document.getElementsByClassName("alectryon-sentence"));
|
||||
slideshow.sentences.forEach(function (s, idx) {
|
||||
s.addEventListener('click', handleClick, false);
|
||||
s.alectryon_index = idx;
|
||||
});
|
||||
}
|
||||
|
||||
slideshow.start = start;
|
||||
slideshow.end = unhighlight;
|
||||
slideshow.navigate = navigate;
|
||||
slideshow.next = function() { navigate(slideshow.pos + 1); };
|
||||
slideshow.previous = function() { navigate(slideshow.pos + -1); };
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
})(Alectryon.slideshow || (Alectryon.slideshow = {}));
|
||||
|
||||
(function (styles) {
|
||||
var styleNames = ["centered", "floating", "windowed"];
|
||||
|
||||
function className(style) {
|
||||
return "alectryon-" + style;
|
||||
}
|
||||
|
||||
function setStyle(style) {
|
||||
var root = document.getElementsByClassName("alectryon-root")[0];
|
||||
styleNames.forEach(function (s) {
|
||||
root.classList.remove(className(s)); });
|
||||
root.classList.add(className(style));
|
||||
}
|
||||
|
||||
function init() {
|
||||
var banner = document.getElementsByClassName("alectryon-banner")[0];
|
||||
if (banner) {
|
||||
banner.append(" Style: ");
|
||||
styleNames.forEach(function (styleName, idx) {
|
||||
var s = styleName;
|
||||
var a = document.createElement("a");
|
||||
a.onclick = function() { setStyle(s); };
|
||||
a.append(styleName);
|
||||
if (idx > 0) banner.append("; ");
|
||||
banner.appendChild(a);
|
||||
});
|
||||
banner.append(".");
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
styles.setStyle = setStyle;
|
||||
})(Alectryon.styles || (Alectryon.styles = {}));
|
||||
})(Alectryon || (Alectryon = {}));
|
||||
|
||||
function setHidden(elements, isVisible, token) {
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
if (isVisible) {
|
||||
elements[i].classList.remove(token)
|
||||
} else {
|
||||
elements[i].classList.add(token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleShowTypes(checkbox) {
|
||||
setHidden(document.getElementsByClassName("alectryon-io"), checkbox.checked, "type-info-hidden")
|
||||
}
|
||||
|
||||
function toggleShowGoals(checkbox) {
|
||||
setHidden(document.getElementsByClassName("alectryon-io"), checkbox.checked, "output-hidden")
|
||||
}
|
||||
77
doc/array.md
Normal file
77
doc/array.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Arrays
|
||||
|
||||
The `Array` type implements a *dynamic* (aka growable) array.
|
||||
It is defined as
|
||||
```lean
|
||||
# namespace hidden
|
||||
structure Array (α : Type u) where
|
||||
data : List α
|
||||
# end hidden
|
||||
```
|
||||
but its execution time representation is optimized, and it is similar to C++ `std::vector<T>` and Rust `Vec<T>`.
|
||||
The Lean type checker has no special support for reducing `Array`s.
|
||||
|
||||
You can create arrays in several ways. You can create a small array by listing consecutive values between
|
||||
`#[` and `]` and separated by commas, as shown in the following examples.
|
||||
|
||||
```lean
|
||||
#check #[1, 2, 3] -- Array Nat
|
||||
|
||||
#check #[] -- Array ?m
|
||||
```
|
||||
|
||||
The type of the array elements is inferred from the literals used and must be consistent.
|
||||
```lean
|
||||
#check #["hello", "world"] -- Array String
|
||||
|
||||
-- The following is not valid
|
||||
#check_failure #[10, "hello"]
|
||||
```
|
||||
Recall that the command `#check_failure <term>` only succeeds when the given term is not type correct.
|
||||
|
||||
To create an array of size `n` in which all the elements are initialized to some value `a`, use `mkArray`.
|
||||
```lean
|
||||
#eval mkArray 5 'a'
|
||||
-- #['a', 'a', 'a', 'a', 'a']
|
||||
```
|
||||
|
||||
## Accessing elements
|
||||
|
||||
You can access array elements by using brackets (`[` and `]`).
|
||||
```lean
|
||||
def f (a : Array Nat) (i : Fin a.size) :=
|
||||
a[i] + a[i]
|
||||
```
|
||||
Note that the index `i` has type `Fin a.size`, i.e., it is natural number less than `a.size`.
|
||||
You can also write
|
||||
```lean
|
||||
def f (a : Array Nat) (i : Nat) (h : i < a.size) :=
|
||||
a[i] + a[i]
|
||||
```
|
||||
The bracket operator is whitespace sensitive.
|
||||
|
||||
```lean
|
||||
def f (xs : List Nat) : List Nat :=
|
||||
xs ++ xs
|
||||
|
||||
def as : Array Nat :=
|
||||
#[1, 2, 3, 4]
|
||||
|
||||
def idx : Fin 4 :=
|
||||
2
|
||||
|
||||
#eval f [1, 2, 3] -- This is a function application
|
||||
|
||||
#eval as[idx] -- This is an array access
|
||||
```
|
||||
The notation `a[i]` has two variants: `a[i]!` and `a[i]?`. In both cases, `i` has type `Nat`. The first one
|
||||
produces a panic error message if the index `i` is out of bounds. The latter returns an `Option` type.
|
||||
|
||||
```lean
|
||||
#eval #['a', 'b', 'c'][1]?
|
||||
-- some 'b'
|
||||
#eval #['a', 'b', 'c'][5]?
|
||||
-- none
|
||||
#eval #['a', 'b', 'c'][1]!
|
||||
-- 'b!
|
||||
```
|
||||
47
doc/autobound.md
Normal file
47
doc/autobound.md
Normal file
@@ -0,0 +1,47 @@
|
||||
## Auto Bound Implicit Arguments
|
||||
|
||||
In the previous section, we have shown how implicit arguments make functions more convenient to use.
|
||||
However, functions such as `compose` are still quite verbose to define. Note that the universe
|
||||
polymorphic `compose` is even more verbose than the one previously defined.
|
||||
|
||||
```lean
|
||||
universe u v w
|
||||
def compose {α : Type u} {β : Type v} {γ : Type w}
|
||||
(g : β → γ) (f : α → β) (x : α) : γ :=
|
||||
g (f x)
|
||||
```
|
||||
|
||||
You can avoid the `universe` command by providing the universe parameters when defining `compose`.
|
||||
|
||||
```lean
|
||||
def compose.{u, v, w}
|
||||
{α : Type u} {β : Type v} {γ : Type w}
|
||||
(g : β → γ) (f : α → β) (x : α) : γ :=
|
||||
g (f x)
|
||||
```
|
||||
|
||||
Lean 4 supports a new feature called *auto bound implicit arguments*. It makes functions such as
|
||||
`compose` much more convenient to write. When Lean processes the header of a declaration,
|
||||
any unbound identifier is automatically added as an implicit argument *if* it is a single lower case or
|
||||
greek letter. With this feature, we can write `compose` as
|
||||
|
||||
```lean
|
||||
def compose (g : β → γ) (f : α → β) (x : α) : γ :=
|
||||
g (f x)
|
||||
|
||||
#check @compose
|
||||
-- {β : Sort u_1} → {γ : Sort u_2} → {α : Sort u_3} → (β → γ) → (α → β) → α → γ
|
||||
```
|
||||
Note that, Lean inferred a more general type using `Sort` instead of `Type`.
|
||||
|
||||
Although we love this feature and use it extensively when implementing Lean,
|
||||
we realize some users may feel uncomfortable with it. Thus, you can disable it using
|
||||
the command `set_option autoImplicit false`.
|
||||
```lean
|
||||
set_option autoImplicit false
|
||||
/- The following definition produces `unknown identifier` errors -/
|
||||
-- def compose (g : β → γ) (f : α → β) (x : α) : γ :=
|
||||
-- g (f x)
|
||||
```
|
||||
The Lean language server provides [semantic highlighting](./semantic_highlighting.md) information to editors, and it provides
|
||||
visual feedback whether an identifier has been interpreted as an auto bound implicit argument.
|
||||
14
doc/bin/README.md
Normal file
14
doc/bin/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
Lean binary distribution
|
||||
------------------------
|
||||
|
||||
The binary distribution package contains:
|
||||
|
||||
- Lean executable (located in the sub-directory bin)
|
||||
- Standard library (located in the sub-directory lib/lean/library)
|
||||
|
||||
Assuming you are in the same directory this file is located,
|
||||
the following command executes a simple set of examples
|
||||
|
||||
% bin/lean examples/ex.lean
|
||||
|
||||
For more information on Lean and supported editors, please see https://lean-lang.org/documentation/.
|
||||
21
doc/book.toml
Normal file
21
doc/book.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[book]
|
||||
authors = ["Leonardo de Moura", "Sebastian Ullrich"]
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "."
|
||||
title = "Lean Manual"
|
||||
|
||||
[build]
|
||||
build-dir = "out"
|
||||
|
||||
[output.html]
|
||||
git-repository-url = "https://github.com/leanprover/lean4"
|
||||
additional-css = ["alectryon.css", "pygments.css"]
|
||||
additional-js = ["alectryon.js"]
|
||||
|
||||
[output.html.fold]
|
||||
enable = true
|
||||
level = 0
|
||||
|
||||
[output.html.playground.boring-prefixes]
|
||||
lean = "# "
|
||||
1
doc/bool.md
Normal file
1
doc/bool.md
Normal file
@@ -0,0 +1 @@
|
||||
# Booleans
|
||||
25
doc/builtintypes.md
Normal file
25
doc/builtintypes.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Builtin Types
|
||||
|
||||
## Numeric Operations
|
||||
|
||||
Lean supports the basic mathematical operations you’d expect for all of the number types: addition, subtraction, multiplication, division, and remainder.
|
||||
The following code shows how you’d use each one in a `def` commands:
|
||||
|
||||
```lean
|
||||
-- addition
|
||||
def sum := 5 + 10
|
||||
|
||||
-- subtraction
|
||||
def difference := 95.5 - 4.3
|
||||
|
||||
-- multiplication
|
||||
def product := 4 * 30
|
||||
|
||||
-- division
|
||||
def quotient := 53.7 / 32.2
|
||||
|
||||
-- remainder/modulo
|
||||
def modulo := 43 % 5
|
||||
```
|
||||
|
||||
Each expression in these statements uses a mathematical operator and evaluates to a single value.
|
||||
1
doc/char.md
Normal file
1
doc/char.md
Normal file
@@ -0,0 +1 @@
|
||||
# Characters
|
||||
884
doc/declarations.md
Normal file
884
doc/declarations.md
Normal file
@@ -0,0 +1,884 @@
|
||||
# Declarations
|
||||
|
||||
-- TODO (fix)
|
||||
|
||||
Declaration Names
|
||||
=================
|
||||
|
||||
A declaration name is a hierarchical [identifier](lexical_structure.md#identifiers) that is interpreted relative to the current namespace as well as (during lookup) to the set of open namespaces.
|
||||
|
||||
```lean
|
||||
namespace A
|
||||
opaque B.c : Nat
|
||||
#print B.c -- opaque A.B.c : Nat
|
||||
end A
|
||||
|
||||
#print A.B.c -- opaque A.B.c : Nat
|
||||
open A
|
||||
#print B.c -- opaque A.B.c : Nat
|
||||
```
|
||||
|
||||
Declaration names starting with an underscore are reserved for internal use. Names starting with the special atomic name ``_root_`` are interpreted as absolute names.
|
||||
|
||||
```lean
|
||||
opaque a : Nat
|
||||
namespace A
|
||||
opaque a : Int
|
||||
#print _root_.a -- opaque a : Nat
|
||||
#print A.a -- opaque A.a : Int
|
||||
end A
|
||||
```
|
||||
|
||||
Contexts and Telescopes
|
||||
=======================
|
||||
|
||||
When processing user input, Lean first parses text to a raw expression format. It then uses background information and type constants to disambiguate overloaded symbols and infer implicit arguments, resulting in a fully-formed expression. This process is known as *elaboration*.
|
||||
|
||||
As hinted in [Expression Syntax](expressions.md#expression_syntax),
|
||||
expressions are parsed and elaborated with respect to an *environment*
|
||||
and a *local context*. Roughly speaking, an environment represents the
|
||||
state of Lean at the point where an expression is parsed, including
|
||||
previously declared axioms, constants, definitions, and theorems. In a
|
||||
given environment, a *local context* consists of a sequence ``(a₁ :
|
||||
α₁) (a₂ : α₂) ... (aₙ : αₙ)`` where each ``aᵢ`` is a name denoting a
|
||||
local constant and each ``αᵢ`` is an expression of type ``Sort u`` for
|
||||
some ``u`` which can involve elements of the environment and the local
|
||||
constants ``aⱼ`` for ``j < i``.
|
||||
|
||||
Intuitively, a local context is a list of variables that are held constant while an expression is being elaborated. Consider the following
|
||||
|
||||
```lean
|
||||
def f (a b : Nat) : Nat → Nat := fun c => a + (b + c)
|
||||
```
|
||||
|
||||
Here the expression ``fun c => a + (b + c)`` is elaborated in the context ``(a : Nat) (b : Nat)`` and the expression ``a + (b + c)`` is elaborated in the context ``(a : Nat) (b : Nat) (c : Nat)``. If you replace the expression ``a + (b + c)`` with an underscore, the error message from Lean will include the current *goal*:
|
||||
|
||||
```
|
||||
a b c : Nat
|
||||
⊢ Nat
|
||||
```
|
||||
|
||||
Here ``a b c : Nat`` indicates the local context, and the second ``Nat`` indicates the expected type of the result.
|
||||
|
||||
A *context* is sometimes called a *telescope*, but the latter is used more generally to include a sequence of declarations occurring relative to a given context. For example, relative to the context ``(a₁ : α₁) (a₂ : α₂) ... (aₙ : αₙ)``, the types ``βᵢ`` in a telescope ``(b₁ : β₁) (b₂ : β₂) ... (bₙ : βₙ)`` can refer to ``a₁, ..., aₙ``. Thus a context can be viewed as a telescope relative to the empty context.
|
||||
|
||||
Telescopes are often used to describe a list of arguments, or parameters, to a declaration. In such cases, it is often notationally convenient to let ``(a : α)`` stand for a telescope rather than just a single argument. In general, the annotations described in [Implicit Arguments](expressions.md#implicit_arguments) can be used to mark arguments as implicit.
|
||||
|
||||
.. _basic_declarations:
|
||||
|
||||
Basic Declarations
|
||||
==================
|
||||
|
||||
Lean provides ways of adding new objects to the environment. The following provide straightforward ways of declaring new objects:
|
||||
|
||||
* ``axiom c : α`` : declare a constant named ``c`` of type ``α``, it is postulating that `α` is not an empty type.
|
||||
* ``def c : α := v`` : defines ``c`` to denote ``v``, which should have type ``α``.
|
||||
* ``theorem c : p := v`` : similar to ``def``, but intended to be used when ``p`` is a proposition.
|
||||
* ``opaque c : α (:= v)?`` : declares a opaque constant named ``c`` of type ``α``, the optional value `v` is must have type `α`
|
||||
and can be viewed as a certificate that ``α`` is not an empty type. If the value is not provided, Lean tries to find one
|
||||
using a procedure based on type class resolution. The value `v` is hidden from the type checker. You can assume that
|
||||
Lean "forgets" `v` after type checking this kind of declaration.
|
||||
|
||||
It is sometimes useful to be able to simulate a definition or theorem without naming it or adding it to the environment.
|
||||
|
||||
* ``example : α := t`` : elaborates ``t`` and checks that it has sort ``α`` (often a proposition), without adding it to the environment.
|
||||
|
||||
In ``def``, the type (``α`` or ``p``, respectively) can be omitted when it can be inferred by Lean. Constants declared with ``theorem`` are marked as ``irreducible``.
|
||||
|
||||
Any of ``def``, ``theorem``, ``axiom``, or ``example`` can take a list of arguments (that is, a context) before the colon. If ``(a : α)`` is a context, the definition ``def foo (a : α) : β := t``
|
||||
is interpreted as ``def foo : (a : α) → β := fun a : α => t``. Similarly, a theorem ``theorem foo (a : α) : p := t`` is interpreted as ``theorem foo : ∀ a : α, p := fun a : α => t``.
|
||||
|
||||
```lean
|
||||
opaque c : Nat
|
||||
opaque d : Nat
|
||||
axiom cd_eq : c = d
|
||||
|
||||
def foo : Nat := 5
|
||||
def bar := 6
|
||||
def baz (x y : Nat) (s : List Nat) := [x, y] ++ s
|
||||
|
||||
theorem foo_eq_five : foo = 5 := rfl
|
||||
theorem baz_theorem (x y : Nat) : baz x y [] = [x, y] := rfl
|
||||
|
||||
example (x y : Nat) : baz x y [] = [x, y] := rfl
|
||||
```
|
||||
|
||||
Inductive Types
|
||||
===============
|
||||
|
||||
Lean's axiomatic foundation allows users to declare arbitrary
|
||||
inductive families, following the pattern described by [Dybjer]_. To
|
||||
make the presentation more manageable, we first describe inductive
|
||||
*types*, and then describe the generalization to inductive *families*
|
||||
in the next section. The declaration of an inductive type has the
|
||||
following form:
|
||||
|
||||
```
|
||||
inductive Foo (a : α) where
|
||||
| constructor₁ : (b : β₁) → Foo a
|
||||
| constructor₂ : (b : β₂) → Foo a
|
||||
...
|
||||
| constructorₙ : (b : βₙ) → Foo a
|
||||
```
|
||||
|
||||
Here ``(a : α)`` is a context and each ``(b : βᵢ)`` is a telescope in the context ``(a : α)`` together with ``Foo``, subject to the following constraints.
|
||||
|
||||
Suppose the telescope ``(b : βᵢ)`` is ``(b₁ : βᵢ₁) ... (bᵤ : βᵢᵤ)``. Each argument in the telescope is either *nonrecursive* or *recursive*.
|
||||
|
||||
- An argument ``(bⱼ : βᵢⱼ)`` is *nonrecursive* if ``βᵢⱼ`` does not refer to ``foo,`` the inductive type being defined. In that case, ``βᵢⱼ`` can be any type, so long as it does not refer to any nonrecursive arguments.
|
||||
|
||||
- An argument ``(bⱼ : βᵢⱼ)`` is *recursive* if it ``βᵢⱼ`` of the form ``Π (d : δ), foo`` where ``(d : δ)`` is a telescope which does not refer to ``foo`` or any nonrecursive arguments.
|
||||
|
||||
The inductive type ``foo`` represents a type that is freely generated by the constructors. Each constructor can take arbitrary data and facts as arguments (the nonrecursive arguments), as well as indexed sequences of elements of ``foo`` that have been previously constructed (the recursive arguments). In set theoretic models, such sets can be represented by well-founded trees labeled by the constructor data, or they can defined using other transfinite or impredicative means.
|
||||
|
||||
The declaration of the type ``foo`` as above results in the addition of the following constants to the environment:
|
||||
|
||||
- the *type former* ``foo : Π (a : α), Sort u``
|
||||
- for each ``i``, the *constructor* ``foo.constructorᵢ : Π (a : α) (b : βᵢ), foo a``
|
||||
- the *eliminator* ``foo.rec``, which takes arguments
|
||||
|
||||
+ ``(a : α)`` (the parameters)
|
||||
+ ``{C : foo a → Type u}`` (the *motive* of the elimination)
|
||||
+ for each ``i``, the *minor premise* corresponding to ``constructorᵢ``
|
||||
+ ``(x : foo)`` (the *major premise*)
|
||||
|
||||
and returns an element of ``C x``. Here, The ith minor premise is a function which takes
|
||||
|
||||
+ ``(b : βᵢ)`` (the arguments to the constructor)
|
||||
+ an argument of type ``Π (d : δ), C (bⱼ d)`` corresponding to each recursive argument ``(bⱼ : βᵢⱼ)``, where ``βᵢⱼ`` is of the form ``Π (d : δ), foo`` (the recursive values of the function being defined)
|
||||
|
||||
and returns an element of ``C (constructorᵢ a b)``, the intended value of the function at ``constructorᵢ a b``.
|
||||
|
||||
The eliminator represents a principle of recursion: to construct an element of ``C x`` where ``x : foo a``, it suffices to consider each of the cases where ``x`` is of the form ``constructorᵢ a b`` and to provide an auxiliary construction in each case. In the case where some of the arguments to ``constructorᵢ`` are recursive, we can assume that we have already constructed values of ``C y`` for each value ``y`` constructed at an earlier stage.
|
||||
|
||||
Under the propositions-as-type correspondence, when ``C x`` is an element of ``Prop``, the eliminator represents a principle of induction. In order to show ``∀ x, C x``, it suffices to show that ``C`` holds for each constructor, under the inductive hypothesis that it holds for all recursive inputs to the constructor.
|
||||
|
||||
The eliminator and constructors satisfy the following identities, in which all the arguments are shown explicitly. Suppose we set ``F := foo.rec a C f₁ ... fₙ``. Then for each constructor, we have the definitional reduction:
|
||||
|
||||
```
|
||||
F (constructorᵢ a b) = fᵢ b ... (fun d : δᵢⱼ => F (bⱼ d)) ...
|
||||
```
|
||||
|
||||
where the ellipses include one entry for each recursive argument.
|
||||
|
||||
Below are some common examples of inductive types, many of which are defined in the core library.
|
||||
|
||||
```lean
|
||||
namespace Hide
|
||||
universe u v
|
||||
|
||||
-- BEGIN
|
||||
inductive Empty : Type
|
||||
|
||||
inductive Unit : Type
|
||||
| unit : Unit
|
||||
|
||||
inductive Bool : Type
|
||||
| false : Bool
|
||||
| true : Bool
|
||||
|
||||
inductive Prod (α : Type u) (β : Type v) : Type (max u v)
|
||||
| mk : α → β → Prod α β
|
||||
|
||||
inductive Sum (α : Type u) (β : Type v)
|
||||
| inl : α → Sum α β
|
||||
| inr : β → Sum α β
|
||||
|
||||
inductive Sigma (α : Type u) (β : α → Type v)
|
||||
| mk : (a : α) → β a → Sigma α β
|
||||
|
||||
inductive false : Prop
|
||||
|
||||
inductive True : Prop
|
||||
| trivial : True
|
||||
|
||||
inductive And (p q : Prop) : Prop
|
||||
| intro : p → q → And p q
|
||||
|
||||
inductive Or (p q : Prop) : Prop
|
||||
| inl : p → Or p q
|
||||
| inr : q → Or p q
|
||||
|
||||
inductive Exists (α : Type u) (p : α → Prop) : Prop
|
||||
| intro : ∀ x : α, p x → Exists α p
|
||||
|
||||
inductive Subtype (α : Type u) (p : α → Prop) : Type u
|
||||
| intro : ∀ x : α, p x → Subtype α p
|
||||
|
||||
inductive Nat : Type
|
||||
| zero : Nat
|
||||
| succ : Nat → Nat
|
||||
|
||||
inductive List (α : Type u)
|
||||
| nil : List α
|
||||
| cons : α → List α → List α
|
||||
|
||||
-- full binary tree with nodes and leaves labeled from α
|
||||
inductive BinTree (α : Type u)
|
||||
| leaf : α → BinTree α
|
||||
| node : BinTree α → α → BinTree α → BinTree α
|
||||
|
||||
-- every internal node has subtrees indexed by Nat
|
||||
inductive CBT (α : Type u)
|
||||
| leaf : α → CBT α
|
||||
| node : (Nat → CBT α) → CBT α
|
||||
-- END
|
||||
end Hide
|
||||
```
|
||||
|
||||
Note that in the syntax of the inductive definition ``Foo``, the context ``(a : α)`` is left implicit. In other words, constructors and recursive arguments are written as though they have return type ``Foo`` rather than ``Foo a``.
|
||||
|
||||
Elements of the context ``(a : α)`` can be marked implicit as described in [Implicit Arguments](#implicit.md#implicit_arguments). These annotations bear only on the type former, ``Foo``. Lean uses a heuristic to determine which arguments to the constructors should be marked implicit, namely, an argument is marked implicit if it can be inferred from the type of a subsequent argument. If the annotation ``{}`` appears after the constructor, a argument is marked implicit if it can be inferred from the type of a subsequent argument *or the return type*. For example, it is useful to let ``nil`` denote the empty list of any type, since the type can usually be inferred in the context in which it appears. These heuristics are imperfect, and you may sometimes wish to define your own constructors in terms of the default ones. In that case, use the ``[match_pattern]`` [attribute](TODO: missing link) to ensure that these will be used appropriately by the [Equation Compiler](#the-equation-compiler).
|
||||
|
||||
There are restrictions on the universe ``u`` in the return type ``Sort u`` of the type former. There are also restrictions on the universe ``u`` in the return type ``Sort u`` of the motive of the eliminator. These will be discussed in the next section in the more general setting of inductive families.
|
||||
|
||||
Lean allows some additional syntactic conveniences. You can omit the return type of the type former, ``Sort u``, in which case Lean will infer the minimal possible nonzero value for ``u``. As with function definitions, you can list arguments to the constructors before the colon. In an enumerated type (that is, one where the constructors have no arguments), you can also leave out the return type of the constructors.
|
||||
|
||||
```lean
|
||||
namespace Hide
|
||||
universe u
|
||||
|
||||
-- BEGIN
|
||||
inductive Weekday
|
||||
| sunday | monday | tuesday | wednesday
|
||||
| thursday | friday | saturday
|
||||
|
||||
inductive Nat
|
||||
| zero
|
||||
| succ (n : Nat) : Nat
|
||||
|
||||
inductive List (α : Type u)
|
||||
| nil : List α
|
||||
| cons (a : α) (l : List α) : List α
|
||||
|
||||
@[match_pattern]
|
||||
def List.nil' (α : Type u) : List α := List.nil
|
||||
|
||||
def length {α : Type u} : List α → Nat
|
||||
| (List.nil' _) => 0
|
||||
| (List.cons a l) => 1 + length l
|
||||
-- END
|
||||
|
||||
end Hide
|
||||
```
|
||||
|
||||
The type former, constructors, and eliminator are all part of Lean's axiomatic foundation, which is to say, they are part of the trusted kernel. In addition to these axiomatically declared constants, Lean automatically defines some additional objects in terms of these, and adds them to the environment. These include the following:
|
||||
|
||||
- ``Foo.recOn`` : a variant of the eliminator, in which the major premise comes first
|
||||
- ``Foo.casesOn`` : a restricted version of the eliminator which omits any recursive calls
|
||||
- ``Foo.noConfusionType``, ``Foo.noConfusion`` : functions which witness the fact that the inductive type is freely generated, i.e. that the constructors are injective and that distinct constructors produce distinct objects
|
||||
- ``Foo.below``, ``Foo.ibelow`` : functions used by the equation compiler to implement structural recursion
|
||||
- ``instance : SizeOf Foo`` : a measure which can be used for well-founded recursion
|
||||
|
||||
Note that it is common to put definitions and theorems related to a datatype ``foo`` in a namespace of the same name. This makes it possible to use projection notation described in [Structures](struct.md#structures) and [Namespaces](namespaces.md#namespaces).
|
||||
|
||||
```lean
|
||||
namespace Hide
|
||||
universe u
|
||||
|
||||
-- BEGIN
|
||||
inductive Nat
|
||||
| zero
|
||||
| succ (n : Nat) : Nat
|
||||
|
||||
#check Nat
|
||||
#check @Nat.rec
|
||||
#check Nat.zero
|
||||
#check Nat.succ
|
||||
|
||||
#check @Nat.recOn
|
||||
#check @Nat.casesOn
|
||||
#check @Nat.noConfusionType
|
||||
#check @Nat.noConfusion
|
||||
#check @Nat.brecOn
|
||||
#check Nat.below
|
||||
#check Nat.ibelow
|
||||
#check Nat._sizeOf_1
|
||||
|
||||
-- END
|
||||
|
||||
end Hide
|
||||
```
|
||||
|
||||
.. _inductive_families:
|
||||
|
||||
Inductive Families
|
||||
==================
|
||||
|
||||
In fact, Lean implements a slight generalization of the inductive types described in the previous section, namely, inductive *families*. The declaration of an inductive family in Lean has the following form:
|
||||
|
||||
```
|
||||
inductive Foo (a : α) : Π (c : γ), Sort u
|
||||
| constructor₁ : Π (b : β₁), Foo t₁
|
||||
| constructor₂ : Π (b : β₂), Foo t₂
|
||||
...
|
||||
| constructorₙ : Π (b : βₙ), Foo tₙ
|
||||
```
|
||||
|
||||
Here ``(a : α)`` is a context, ``(c : γ)`` is a telescope in context ``(a : α)``, each ``(b : βᵢ)`` is a telescope in the context ``(a : α)`` together with ``(Foo : Π (c : γ), Sort u)`` subject to the constraints below, and each ``tᵢ`` is a tuple of terms in the context ``(a : α) (b : βᵢ)`` having the types ``γ``. Instead of defining a single inductive type ``Foo a``, we are now defining a family of types ``Foo a c`` indexed by elements ``c : γ``. Each constructor, ``constructorᵢ``, places its result in the type ``Foo a tᵢ``, the member of the family with index ``tᵢ``.
|
||||
|
||||
The modifications to the scheme in the previous section are straightforward. Suppose the telescope ``(b : βᵢ)`` is ``(b₁ : βᵢ₁) ... (bᵤ : βᵢᵤ)``.
|
||||
|
||||
- As before, an argument ``(bⱼ : βᵢⱼ)`` is *nonrecursive* if ``βᵢⱼ`` does not refer to ``Foo,`` the inductive type being defined. In that case, ``βᵢⱼ`` can be any type, so long as it does not refer to any nonrecursive arguments.
|
||||
|
||||
- An argument ``(bⱼ : βᵢⱼ)`` is *recursive* if ``βᵢⱼ`` is of the form ``Π (d : δ), Foo s`` where ``(d : δ)`` is a telescope which does not refer to ``Foo`` or any nonrecursive arguments and ``s`` is a tuple of terms in context ``(a : α)`` and the previous nonrecursive ``bⱼ``'s with types ``γ``.
|
||||
|
||||
The declaration of the type ``Foo`` as above results in the addition of the following constants to the environment:
|
||||
|
||||
- the *type former* ``Foo : Π (a : α) (c : γ), Sort u``
|
||||
- for each ``i``, the *constructor* ``Foo.constructorᵢ : Π (a : α) (b : βᵢ), Foo a tᵢ``
|
||||
- the *eliminator* ``Foo.rec``, which takes arguments
|
||||
|
||||
+ ``(a : α)`` (the parameters)
|
||||
+ ``{C : Π (c : γ), Foo a c → Type u}`` (the motive of the elimination)
|
||||
+ for each ``i``, the minor premise corresponding to ``constructorᵢ``
|
||||
+ ``(x : Foo a)`` (the major premise)
|
||||
|
||||
and returns an element of ``C x``. Here, The ith minor premise is a function which takes
|
||||
|
||||
+ ``(b : βᵢ)`` (the arguments to the constructor)
|
||||
+ an argument of type ``Π (d : δ), C s (bⱼ d)`` corresponding to each recursive argument ``(bⱼ : βᵢⱼ)``, where ``βᵢⱼ`` is of the form ``Π (d : δ), Foo s``
|
||||
|
||||
and returns an element of ``C tᵢ (constructorᵢ a b)``.
|
||||
|
||||
Suppose we set ``F := Foo.rec a C f₁ ... fₙ``. Then for each constructor, we have the definitional reduction, as before:
|
||||
|
||||
```
|
||||
F (constructorᵢ a b) = fᵢ b ... (fun d : δᵢⱼ => F (bⱼ d)) ...
|
||||
```
|
||||
|
||||
where the ellipses include one entry for each recursive argument.
|
||||
|
||||
The following are examples of inductive families.
|
||||
|
||||
```lean
|
||||
namespace Hide
|
||||
universe u
|
||||
|
||||
-- BEGIN
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector 0
|
||||
| succ : Π n, Vector n → Vector (n + 1)
|
||||
|
||||
-- 'IsProd s n' means n is a product of elements of s
|
||||
inductive IsProd (s : Set Nat) : Nat → Prop
|
||||
| base : ∀ n ∈ s, IsProd n
|
||||
| step : ∀ m n, IsProd m → IsProd n → IsProd (m * n)
|
||||
|
||||
inductive Eq {α : Sort u} (a : α) : α → Prop
|
||||
| refl : Eq a
|
||||
-- END
|
||||
|
||||
end Hide
|
||||
```
|
||||
|
||||
We can now describe the constraints on the return type of the type former, ``Sort u``. We can always take ``u`` to be ``0``, in which case we are defining an inductive family of propositions. If ``u`` is nonzero, however, it must satisfy the following constraint: for each type ``βᵢⱼ : Sort v`` occurring in the constructors, we must have ``u ≥ v``. In the set-theoretic interpretation, this ensures that the universe in which the resulting type resides is large enough to contain the inductively generated family, given the number of distinctly-labeled constructors. The restriction does not hold for inductively defined propositions, since these contain no data.
|
||||
|
||||
Putting an inductive family in ``Prop``, however, does impose a restriction on the eliminator. Generally speaking, for an inductive family in ``Prop``, the motive in the eliminator is required to be in ``Prop``. But there is an exception to this rule: you are allowed to eliminate from an inductively defined ``Prop`` to an arbitrary ``Sort`` when there is only one constructor, and each argument to that constructor is either in ``Prop`` or an index. The intuition is that in this case the elimination does not make use of any information that is not already given by the mere fact that the type of argument is inhabited. This special case is known as *singleton elimination*.
|
||||
|
||||
.. _mutual_and_nested_inductive_definitions:
|
||||
|
||||
Mutual and Nested Inductive Definitions
|
||||
=======================================
|
||||
|
||||
Lean supports two generalizations of the inductive families described above, namely, *mutual* and *nested* inductive definitions. These are *not* implemented natively in the kernel. Rather, the definitions are compiled down to the primitive inductive types and families.
|
||||
|
||||
The first generalization allows for multiple inductive types to be defined simultaneously.
|
||||
|
||||
```
|
||||
mutual
|
||||
|
||||
inductive Foo (a : α) : Π (c : γ₁), Sort u
|
||||
| constructor₁₁ : Π (b : β₁₁), Foo a t₁₁
|
||||
| constructor₁₂ : Π (b : β₁₂), Foo a t₁₂
|
||||
...
|
||||
| constructor₁ₙ : Π (b : β₁ₙ), Foo a t₁ₙ
|
||||
|
||||
inductive Bar (a : α) : Π (c : γ₂), Sort u
|
||||
| constructor₂₁ : Π (b : β₂₁), Bar a t₂₁
|
||||
| constructor₂₂ : Π (b : β₂₂), Bar a t₂₂
|
||||
...
|
||||
| constructor₂ₘ : Π (b : β₂ₘ), Bar a t₂ₘ
|
||||
|
||||
end
|
||||
```
|
||||
|
||||
Here the syntax is shown for defining two inductive families, ``Foo`` and ``Bar``, but any number is allowed. The restrictions are almost the same as for ordinary inductive families. For example, each ``(b : βᵢⱼ)`` is a telescope relative to the context ``(a : α)``. The difference is that the constructors can now have recursive arguments whose return types are any of the inductive families currently being defined, in this case ``Foo`` and ``Bar``. Note that all of the inductive definitions share the same parameters ``(a : α)``, though they may have different indices.
|
||||
|
||||
A mutual inductive definition is compiled down to an ordinary inductive definition using an extra finite-valued index to distinguish the components. The details of the internal construction are meant to be hidden from most users. Lean defines the expected type formers ``Foo`` and ``Bar`` and constructors ``constructorᵢⱼ`` from the internal inductive definition. There is no straightforward elimination principle, however. Instead, Lean defines an appropriate ``sizeOf`` measure, meant for use with well-founded recursion, with the property that the recursive arguments to a constructor are smaller than the constructed value.
|
||||
|
||||
The second generalization relaxes the restriction that in the recursive definition of ``Foo``, ``Foo`` can only occur strictly positively in the type of any of its recursive arguments. Specifically, in a nested inductive definition, ``Foo`` can appear as an argument to another inductive type constructor, so long as the corresponding parameter occurs strictly positively in the constructors for *that* inductive type. This process can be iterated, so that additional type constructors can be applied to those, and so on.
|
||||
|
||||
A nested inductive definition is compiled down to an ordinary inductive definition using a mutual inductive definition to define copies of all the nested types simultaneously. Lean then constructs isomorphisms between the mutually defined nested types and their independently defined counterparts. Once again, the internal details are not meant to be manipulated by users. Rather, the type former and constructors are made available and work as expected, while an appropriate ``sizeOf`` measure is generated for use with well-founded recursion.
|
||||
|
||||
```lean
|
||||
universe u
|
||||
-- BEGIN
|
||||
mutual
|
||||
inductive Even : Nat → Prop
|
||||
| even_zero : Even 0
|
||||
| even_succ : ∀ n, Odd n → Even (n + 1)
|
||||
inductive Odd : Nat → Prop
|
||||
| odd_succ : ∀ n, Even n → Odd (n + 1)
|
||||
end
|
||||
|
||||
inductive Tree (α : Type u)
|
||||
| mk : α → List (Tree α) → Tree α
|
||||
|
||||
inductive DoubleTree (α : Type u)
|
||||
| mk : α → List (DoubleTree α) × List (DoubleTree α) → DoubleTree α
|
||||
-- END
|
||||
```
|
||||
|
||||
.. _the_equation_compiler:
|
||||
|
||||
The Equation Compiler
|
||||
=====================
|
||||
|
||||
The equation compiler takes an equational description of a function or proof and tries to define an object meeting that specification. It expects input with the following syntax:
|
||||
|
||||
```
|
||||
def foo (a : α) : Π (b : β), γ
|
||||
| [patterns₁] => t₁
|
||||
...
|
||||
| [patternsₙ] => tₙ
|
||||
```
|
||||
|
||||
Here ``(a : α)`` is a telescope, ``(b : β)`` is a telescope in the context ``(a : α)``, and ``γ`` is an expression in the context ``(a : α) (b : β)`` denoting a ``Type`` or a ``Prop``.
|
||||
|
||||
Each ``patternsᵢ`` is a sequence of patterns of the same length as ``(b : β)``. A pattern is either:
|
||||
|
||||
- a variable, denoting an arbitrary value of the relevant type,
|
||||
- an underscore, denoting a *wildcard* or *anonymous variable*,
|
||||
- an inaccessible term (see below), or
|
||||
- a constructor for the inductive type of the corresponding argument, applied to a sequence of patterns.
|
||||
|
||||
In the last case, the pattern must be enclosed in parentheses.
|
||||
|
||||
Each term ``tᵢ`` is an expression in the context ``(a : α)`` together with the variables introduced on the left-hand side of the token ``=>``. The term ``tᵢ`` can also include recursive calls to ``foo``, as described below. The equation compiler does case splitting on the variables ``(b : β)`` as necessary to match the patterns, and defines ``foo`` so that it has the value ``tᵢ`` in each of the cases. In ideal circumstances (see below), the equations hold definitionally. Whether they hold definitionally or only propositionally, the equation compiler proves the relevant equations and assigns them internal names. They are accessible by the ``rewrite`` and ``simp`` tactics under the name ``foo`` (see [Rewrite](tactics.md#rewrite) and _[TODO: where is simplifier tactic documented?]_. If some of the patterns overlap, the equation compiler interprets the definition so that the first matching pattern applies in each case. Thus, if the last pattern is a variable, it covers all the remaining cases. If the patterns that are presented do not cover all possible cases, the equation compiler raises an error.
|
||||
|
||||
When identifiers are marked with the ``[match_pattern]`` attribute, the equation compiler unfolds them in the hopes of exposing a constructor. For example, this makes it possible to write ``n+1`` and ``0`` instead of ``Nat.succ n`` and ``Nat.zero`` in patterns.
|
||||
|
||||
For a nonrecursive definition involving case splits, the defining equations will hold definitionally. With inductive types like ``Char``, ``String``, and ``Fin n``, a case split would produce definitions with an inordinate number of cases. To avoid this, the equation compiler uses ``if ... then ... else`` instead of ``casesOn`` when defining the function. In this case, the defining equations hold definitionally as well.
|
||||
|
||||
```lean
|
||||
open Nat
|
||||
|
||||
def sub2 : Nat → Nat
|
||||
| zero => 0
|
||||
| succ zero => 0
|
||||
| succ (succ a) => a
|
||||
|
||||
def bar : Nat → List Nat → Bool → Nat
|
||||
| 0, _, false => 0
|
||||
| 0, b :: _, _ => b
|
||||
| 0, [], true => 7
|
||||
| a+1, [], false => a
|
||||
| a+1, [], true => a + 1
|
||||
| a+1, b :: _, _ => a + b
|
||||
|
||||
def baz : Char → Nat
|
||||
| 'A' => 1
|
||||
| 'B' => 2
|
||||
| _ => 3
|
||||
```
|
||||
|
||||
The case where patterns are matched against an argument whose type is an inductive family is known as *dependent pattern matching*. This is more complicated, because the type of the function being defined can impose constraints on the patterns that are matched. In this case, the equation compiler will detect inconsistent cases and rule them out.
|
||||
|
||||
```lean
|
||||
universe u
|
||||
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector α 0
|
||||
| cons : α → Vector α n → Vector α (n+1)
|
||||
|
||||
namespace Vector
|
||||
|
||||
def head : Vector α (n+1) → α
|
||||
| cons h t => h
|
||||
|
||||
def tail : Vector α (n+1) → Vector α n
|
||||
| cons h t => t
|
||||
|
||||
def map (f : α → β → γ) : Vector α n → Vector β n → Vector γ n
|
||||
| nil, nil => nil
|
||||
| cons a va, cons b vb => cons (f a b) (map f va vb)
|
||||
|
||||
end Vector
|
||||
```
|
||||
|
||||
.. _recursive_functions:
|
||||
|
||||
Recursive functions
|
||||
===================
|
||||
|
||||
Lean must ensure that a recursive function terminates, for which there are two strategies: _structural recursion_, in which all recursive calls are made on smaller parts of the input data, and _well-founded recursion_, in which recursive calls are justified by showing that arguments to recursive calls are smaller according to some other measure.
|
||||
|
||||
Structural recursion
|
||||
--------------------
|
||||
|
||||
If the definition of a function contains recursive calls, Lean first tries to interpret the definition as a structural recursion. In order for that to succeed, the recursive arguments must be subterms of the corresponding arguments on the left-hand side.
|
||||
|
||||
The function is then defined using a *course of values* recursion, using automatically generated functions ``below`` and ``brec`` in the namespace corresponding to the inductive type of the recursive argument. In this case the defining equations hold definitionally, possibly with additional case splits.
|
||||
|
||||
```lean
|
||||
namespace Hide
|
||||
|
||||
-- BEGIN
|
||||
def fib : Nat → Nat
|
||||
| 0 => 1
|
||||
| 1 => 1
|
||||
| (n+2) => fib (n+1) + fib n
|
||||
|
||||
def append {α : Type} : List α → List α → List α
|
||||
| [], l => l
|
||||
| h::t, l => h :: append t l
|
||||
|
||||
example : append [(1 : Nat), 2, 3] [4, 5] = [1, 2, 3, 4, 5] => rfl
|
||||
-- END
|
||||
|
||||
end Hide
|
||||
```
|
||||
|
||||
Well-founded recursion
|
||||
---------------------
|
||||
|
||||
If structural recursion fails, the equation compiler falls back on well-founded recursion. It tries to infer an instance of ``SizeOf`` for the type of each argument, and then tries to find a permutation of the arguments such that each recursive call is decreasing under the lexicographic order with respect to ``sizeOf`` measures. Lean uses information in the local context, so you can often provide the relevant proof manually using ``have`` in the body of the definition.
|
||||
|
||||
In the case of well-founded recursion, the equation used to declare the function holds only propositionally, but not definitionally, and can be accessed using ``unfold``, ``simp`` and ``rewrite`` with the function name (for example ``unfold foo`` or ``simp [foo]``, where ``foo`` is the function defined with well-founded recursion).
|
||||
|
||||
```lean
|
||||
namespace Hide
|
||||
open Nat
|
||||
|
||||
-- BEGIN
|
||||
def div : Nat → Nat → Nat
|
||||
| x, y =>
|
||||
if h : 0 < y ∧ y ≤ x then
|
||||
have : x - y < x :=
|
||||
sub_lt (Nat.lt_of_lt_of_le h.left h.right) h.left
|
||||
div (x - y) y + 1
|
||||
else
|
||||
0
|
||||
|
||||
example (x y : Nat) :
|
||||
div x y = if 0 < y ∧ y ≤ x then div (x - y) y + 1 else 0 :=
|
||||
by rw [div]; rfl
|
||||
-- END
|
||||
|
||||
end Hide
|
||||
```
|
||||
|
||||
If Lean cannot find a permutation of the arguments for which all recursive calls are decreasing, it will print a table that contains, for every recursive call, which arguments Lean could prove to be decreasing. For example, a function with three recursive calls and four parameters might cause the following message to be printed
|
||||
|
||||
```
|
||||
example.lean:37:0-43:31: error: Could not find a decreasing measure.
|
||||
The arguments relate at each recursive call as follows:
|
||||
(<, ≤, =: relation proved, ? all proofs failed, _: no proof attempted)
|
||||
x1 x2 x3 x4
|
||||
1) 39:6-27 = = _ =
|
||||
2) 40:6-25 = ? _ <
|
||||
3) 41:6-25 < _ _ _
|
||||
Please use `termination_by` to specify a decreasing measure.
|
||||
```
|
||||
|
||||
This table should be read as follows:
|
||||
|
||||
* In the first recursive call, in line 39, arguments 1, 2 and 4 are equal to the function's parameters.
|
||||
* The second recursive call, in line 40, has an equal first argument, a smaller fourth argument, and nothing could be inferred for the second argument.
|
||||
* The third recursive call, in line 41, has a decreasing first argument.
|
||||
* No other proofs were attempted, either because the parameter has a type without a non-trivial ``WellFounded`` instance (parameter 3), or because it is already clear that no decreasing measure can be found.
|
||||
|
||||
|
||||
Lean will print the termination argument it found if ``set_option showInferredTerminationBy true`` is set.
|
||||
|
||||
If Lean does not find the termination argument, or if you want to be explicit, you can append a `termination_by` clause to the function definition, after the function's body, but before the `where` clause if present. It is of the form
|
||||
```
|
||||
termination_by e
|
||||
```
|
||||
where ``e`` is an expression that depends on the parameters of the function and should be decreasing at each recursive call. The type of `e` should be an instance of the class ``WellFoundedRelation``, which determines how to compare two values of that type.
|
||||
|
||||
If ``f`` has parameters “after the ``:``” (for example when defining functions via patterns using `|`), then these can be brought into scope using the syntax
|
||||
```
|
||||
termination_by a₁ … aₙ => e
|
||||
```
|
||||
|
||||
By default, Lean uses the tactic ``decreasing_tactic`` when proving that an argument is decreasing; see its documentation for how to globally extend it. You can also choose to use a different tactic for a given function definition with the clause
|
||||
```
|
||||
decreasing_by <tac>
|
||||
```
|
||||
which should come after ``termination_by`, if present.
|
||||
|
||||
|
||||
Note that recursive definitions can in general require nested recursions, that is, recursion on different arguments of ``foo`` in the template above. The equation compiler handles this by abstracting later arguments, and recursively defining higher-order functions to meet the specification.
|
||||
|
||||
Mutual recursion
|
||||
----------------
|
||||
|
||||
The equation compiler also allows mutual recursive definitions, with a syntax similar to that of [Mutual and Nested Inductive Definitions](#mutual-and-nested-inductive-definitions). Mutual definitions are always compiled using well-founded recursion, and so once again the defining equations hold only propositionally.
|
||||
|
||||
```lean
|
||||
mutual
|
||||
def even : Nat → Bool
|
||||
| 0 => true
|
||||
| a+1 => odd a
|
||||
def odd : Nat → Bool
|
||||
| 0 => false
|
||||
| a+1 => even a
|
||||
end
|
||||
|
||||
example (a : Nat) : even (a + 1) = odd a :=
|
||||
by simp [even]
|
||||
|
||||
example (a : Nat) : odd (a + 1) = even a :=
|
||||
by simp [odd]
|
||||
```
|
||||
|
||||
Well-founded recursion is especially useful with [Mutual and Nested Inductive Definitions](#mutual-and-nested-inductive-definitions), since it provides the canonical way of defining functions on these types.
|
||||
|
||||
```lean
|
||||
mutual
|
||||
inductive Even : Nat → Prop
|
||||
| even_zero : Even 0
|
||||
| even_succ : ∀ n, Odd n → Even (n + 1)
|
||||
inductive Odd : Nat → Prop
|
||||
| odd_succ : ∀ n, Even n → Odd (n + 1)
|
||||
end
|
||||
|
||||
open Even Odd
|
||||
|
||||
theorem not_odd_zero : ¬ Odd 0 := fun x => nomatch x
|
||||
|
||||
mutual
|
||||
theorem even_of_odd_succ : ∀ n, Odd (n + 1) → Even n
|
||||
| _, odd_succ n h => h
|
||||
theorem odd_of_even_succ : ∀ n, Even (n + 1) → Odd n
|
||||
| _, even_succ n h => h
|
||||
end
|
||||
|
||||
inductive Term
|
||||
| const : String → Term
|
||||
| app : String → List Term → Term
|
||||
|
||||
open Term
|
||||
|
||||
mutual
|
||||
def num_consts : Term → Nat
|
||||
| .const n => 1
|
||||
| .app n ts => num_consts_lst ts
|
||||
def num_consts_lst : List Term → Nat
|
||||
| [] => 0
|
||||
| t::ts => num_consts t + num_consts_lst ts
|
||||
end
|
||||
```
|
||||
|
||||
In a set of mutually recursive function, either all or no functions must have an explicit termination argument (``termination_by``). A change of the default termination tactic (``decreasing_by``) only affects the proofs about the recursive calls of that function, not the other functions in the group.
|
||||
|
||||
```
|
||||
mutual
|
||||
theorem even_of_odd_succ : ∀ n, Odd (n + 1) → Even n
|
||||
| _, odd_succ n h => h
|
||||
termination_by n h => h
|
||||
decreasing_by decreasing_tactic
|
||||
|
||||
theorem odd_of_even_succ : ∀ n, Even (n + 1) → Odd n
|
||||
| _, even_succ n h => h
|
||||
termination_by n h => h
|
||||
end
|
||||
```
|
||||
|
||||
Another way to express mutual recursion is using local function definitions in ``where`` or ``let rec`` clauses: these can be mutually recursive with each other and their containing function:
|
||||
|
||||
```
|
||||
theorem even_of_odd_succ : ∀ n, Odd (n + 1) → Even n
|
||||
| _, odd_succ n h => h
|
||||
termination_by n h => h
|
||||
where
|
||||
theorem odd_of_even_succ : ∀ n, Even (n + 1) → Odd n
|
||||
| _, even_succ n h => h
|
||||
termination_by n h => h
|
||||
```
|
||||
|
||||
.. _match_expressions:
|
||||
|
||||
Match Expressions
|
||||
=================
|
||||
|
||||
Lean supports a ``match ... with ...`` construct similar to ones found in most functional programming languages. The syntax is as follows:
|
||||
|
||||
```
|
||||
match t₁, ..., tₙ with
|
||||
| p₁₁, ..., p₁ₙ => s₁
|
||||
...
|
||||
| pₘ₁, ..., pₘₙ => sₘ
|
||||
```
|
||||
|
||||
Here ``t₁, ..., tₙ`` are any terms in the context in which the expression appears, the expressions ``pᵢⱼ`` are patterns, and the terms ``sᵢ`` are expressions in the local context together with variables introduced by the patterns on the left-hand side. Each ``sᵢ`` should have the expected type of the entire ``match`` expression.
|
||||
|
||||
Any ``match`` expression is interpreted using the equation compiler, which generalizes ``t₁, ..., tₙ``, defines an internal function meeting the specification, and then applies it to ``t₁, ..., tₙ``. In contrast to the definitions in [The Equation Compiler](declarations.md#the-equation-compiler), the terms ``tᵢ`` are arbitrary terms rather than just variables, and the expression can occur anywhere within a Lean expression, not just at the top level of a definition. Note that the syntax here is somewhat different: both the terms ``tᵢ`` and the patterns ``pᵢⱼ`` are separated by commas.
|
||||
|
||||
```lean
|
||||
def foo (n : Nat) (b c : Bool) :=
|
||||
5 + match n - 5, b && c with
|
||||
| 0, true => 0
|
||||
| m+1, true => m + 7
|
||||
| 0, false => 5
|
||||
| m+1, false => m + 3
|
||||
```
|
||||
|
||||
When a ``match`` has only one line, Lean provides alternative syntax with a destructuring ``let``, as well as a destructuring lambda abstraction. Thus the following definitions all have the same net effect.
|
||||
|
||||
```lean
|
||||
def bar₁ : Nat × Nat → Nat
|
||||
| (m, n) => m + n
|
||||
|
||||
def bar₂ (p : Nat × Nat) : Nat :=
|
||||
match p with | (m, n) => m + n
|
||||
|
||||
def bar₃ : Nat × Nat → Nat :=
|
||||
fun ⟨m, n⟩ => m + n
|
||||
|
||||
def bar₄ (p : Nat × Nat) : Nat :=
|
||||
let ⟨m, n⟩ := p; m + n
|
||||
```
|
||||
|
||||
Information about the term being matched can be preserved in each branch using the syntax `match h : t with`. For example, a user may want to match a term `ns ++ ms : List Nat`, while tracking the hypothesis `ns ++ ms = []` or `ns ++ ms= h :: t` in the respective match arm:
|
||||
|
||||
```lean
|
||||
def foo (ns ms : List Nat) (h1 : ns ++ ms ≠ []) (k : Nat -> Char) : Char :=
|
||||
match h2 : ns ++ ms with
|
||||
-- in this arm, we have the hypothesis `h2 : ns ++ ms = []`
|
||||
| [] => absurd h2 h1
|
||||
-- in this arm, we have the hypothesis `h2 : ns ++ ms = h :: t`
|
||||
| h :: t => k h
|
||||
|
||||
-- '7'
|
||||
#eval foo [7, 8, 9] [] (by decide) Nat.digitChar
|
||||
```
|
||||
|
||||
.. _structures_and_records:
|
||||
|
||||
Structures and Records
|
||||
======================
|
||||
|
||||
The ``structure`` command in Lean is used to define an inductive data type with a single constructor and to define its projections at the same time. The syntax is as follows:
|
||||
|
||||
```
|
||||
structure Foo (a : α) extends Bar, Baz : Sort u :=
|
||||
constructor :: (field₁ : β₁) ... (fieldₙ : βₙ)
|
||||
```
|
||||
|
||||
Here ``(a : α)`` is a telescope, that is, the parameters to the inductive definition. The name ``constructor`` followed by the double colon is optional; if it is not present, the name ``mk`` is used by default. The keyword ``extends`` followed by a list of previously defined structures is also optional; if it is present, an instance of each of these structures is included among the fields to ``Foo``, and the types ``βᵢ`` can refer to their fields as well. The output type, ``Sort u``, can be omitted, in which case Lean infers to smallest non-``Prop`` sort possible. Finally, ``(field₁ : β₁) ... (fieldₙ : βₙ)`` is a telescope relative to ``(a : α)`` and the fields in ``bar`` and ``baz``.
|
||||
|
||||
The declaration above is syntactic sugar for an inductive type declaration, and so results in the addition of the following constants to the environment:
|
||||
|
||||
- the type former : ``Foo : Π (a : α), Sort u``
|
||||
- the single constructor :
|
||||
|
||||
```
|
||||
Foo.constructor : Π (a : α) (toBar : Bar) (toBaz : Baz)
|
||||
(field₁ : β₁) ... (fieldₙ : βₙ), Foo a
|
||||
```
|
||||
|
||||
- the eliminator ``Foo.rec`` for the inductive type with that constructor
|
||||
|
||||
In addition, Lean defines
|
||||
|
||||
- the projections : ``fieldᵢ : Π (a : α) (c : Foo) : βᵢ`` for each ``i``
|
||||
|
||||
where any other fields mentioned in ``βᵢ`` are replaced by the relevant projections from ``c``.
|
||||
|
||||
Given ``c : Foo``, Lean offers the following convenient syntax for the projection ``Foo.fieldᵢ c``:
|
||||
|
||||
- *anonymous projections* : ``c.fieldᵢ``
|
||||
- *numbered projections* : ``c.i``
|
||||
|
||||
These can be used in any situation where Lean can infer that the type of ``c`` is of the form ``Foo a``. The convention for anonymous projections is extended to any function ``f`` defined in the namespace ``Foo``, as described in [Namespaces](namespaces.md).
|
||||
|
||||
Similarly, Lean offers the following convenient syntax for constructing elements of ``Foo``. They are equivalent to ``Foo.constructor b₁ b₂ f₁ f₁ ... fₙ``, where ``b₁ : Bar``, ``b₂ : Baz``, and each ``fᵢ : βᵢ`` :
|
||||
|
||||
- *anonymous constructor*: ``⟨ b₁, b₂, f₁, ..., fₙ ⟩``
|
||||
- *record notation*:
|
||||
|
||||
```
|
||||
{ toBar := b₁, toBaz := b₂, field₁ := f₁, ...,
|
||||
fieldₙ := fₙ : Foo a }
|
||||
```
|
||||
|
||||
The anonymous constructor can be used in any context where Lean can infer that the expression should have a type of the form ``Foo a``. The unicode brackets are entered as ``\<`` and ``\>`` respectively.
|
||||
|
||||
When using record notation, you can omit the annotation ``: Foo a`` when Lean can infer that the expression should have a type of the form ``Foo a``. You can replace either ``toBar`` or ``toBaz`` by assignments to *their* fields as well, essentially acting as though the fields of ``Bar`` and ``Baz`` are simply imported into ``Foo``. Finally, record notation also supports
|
||||
|
||||
- *record updates*: ``{ t with ... fieldᵢ := fᵢ ...}``
|
||||
|
||||
Here ``t`` is a term of type ``Foo a`` for some ``a``. The notation instructs Lean to take values from ``t`` for any field assignment that is omitted from the list.
|
||||
|
||||
Lean also allows you to specify a default value for any field in a structure by writing ``(fieldᵢ : βᵢ := t)``. Here ``t`` specifies the value to use when the field ``fieldᵢ`` is left unspecified in an instance of record notation.
|
||||
|
||||
```lean
|
||||
universe u v
|
||||
|
||||
structure Vec (α : Type u) (n : Nat) :=
|
||||
(l : List α) (h : l.length = n)
|
||||
|
||||
structure Foo (α : Type u) (β : Nat → Type v) : Type (max u v) :=
|
||||
(a : α) (n : Nat) (b : β n)
|
||||
|
||||
structure Bar :=
|
||||
(c : Nat := 8) (d : Nat)
|
||||
|
||||
structure Baz extends Foo Nat (Vec Nat), Bar :=
|
||||
(v : Vec Nat n)
|
||||
|
||||
#check Foo
|
||||
#check @Foo.mk
|
||||
#check @Foo.rec
|
||||
|
||||
#check Foo.a
|
||||
#check Foo.n
|
||||
#check Foo.b
|
||||
|
||||
#check Baz
|
||||
#check @Baz.mk
|
||||
#check @Baz.rec
|
||||
|
||||
#check Baz.toFoo
|
||||
#check Baz.toBar
|
||||
#check Baz.v
|
||||
|
||||
def bzz := Vec.mk [1, 2, 3] rfl
|
||||
|
||||
#check Vec.l bzz
|
||||
#check Vec.h bzz
|
||||
#check bzz.l
|
||||
#check bzz.h
|
||||
#check bzz.1
|
||||
#check bzz.2
|
||||
|
||||
example : Vec Nat 3 := Vec.mk [1, 2, 3] rfl
|
||||
example : Vec Nat 3 := ⟨[1, 2, 3], rfl⟩
|
||||
example : Vec Nat 3 := { l := [1, 2, 3], h := rfl : Vec Nat 3 }
|
||||
example : Vec Nat 3 := { l := [1, 2, 3], h := rfl }
|
||||
|
||||
example : Foo Nat (Vec Nat) := ⟨1, 3, bzz⟩
|
||||
|
||||
example : Baz := ⟨⟨1, 3, bzz⟩, ⟨5, 7⟩, bzz⟩
|
||||
example : Baz := { a := 1, n := 3, b := bzz, c := 5, d := 7, v := bzz}
|
||||
def fzz : Foo Nat (Vec Nat) := {a := 1, n := 3, b := bzz}
|
||||
|
||||
example : Foo Nat (Vec Nat) := { fzz with a := 7 }
|
||||
example : Baz := { fzz with c := 5, d := 7, v := bzz }
|
||||
|
||||
example : Bar := { c := 8, d := 9 }
|
||||
example : Bar := { d := 9 } -- uses the default value for c
|
||||
```
|
||||
|
||||
.. _type_classes:
|
||||
|
||||
Type Classes
|
||||
============
|
||||
|
||||
(Classes and instances. Anonymous instances. Local instances.)
|
||||
|
||||
|
||||
.. [Dybjer] Dybjer, Peter, *Inductive Families*. Formal Aspects of Computing 6, 1994, pages 440-465.
|
||||
29
doc/decltypes.md
Normal file
29
doc/decltypes.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Declaring New Types
|
||||
|
||||
In Lean's library, every concrete type other than the universes and every type constructor other than the dependent function type is
|
||||
an instance of a general family of type constructions known as *inductive types*. It is remarkable that it is possible to develop
|
||||
complex programs and formalize mathematics based on nothing more than the type universes, dependent function types,
|
||||
and inductive types; everything else follows from those.
|
||||
|
||||
Intuitively, an inductive type is built up from a specified list of constructors. In Lean, the basic syntax for specifying such a type is as follows:
|
||||
```
|
||||
inductive NewType where
|
||||
| constructor_1 : ... → NewType
|
||||
| constructor_2 : ... → NewType
|
||||
...
|
||||
| constructor_n : ... → NewType
|
||||
```
|
||||
|
||||
The intuition is that each constructor specifies a way of building new objects of ``NewType``, possibly from previously constructed values.
|
||||
The type ``NewType`` consists of nothing more than the objects that are constructed in this way.
|
||||
|
||||
We will see below that the arguments to the constructors can include objects of type ``NewType``,
|
||||
subject to a certain "positivity" constraint, which guarantees that elements of ``NewType`` are built
|
||||
from the bottom up. Roughly speaking, each ``...`` can be any function type constructed from ``NewType``
|
||||
and previously defined types, in which ``NewType`` appears, if at all, only as the "target" of the function type.
|
||||
|
||||
We will provide a number of examples of inductive types. We will also consider slight generalizations of the scheme above,
|
||||
to mutually defined inductive types, and so-called *inductive families*.
|
||||
|
||||
Every inductive type comes with constructors, which show how to construct an element of the type, and elimination rules,
|
||||
which show how to "use" an element of the type in another construction.
|
||||
1
doc/definitions.md
Normal file
1
doc/definitions.md
Normal file
@@ -0,0 +1 @@
|
||||
# Definitions
|
||||
66
doc/dep.md
Normal file
66
doc/dep.md
Normal file
@@ -0,0 +1,66 @@
|
||||
## What makes dependent type theory dependent?
|
||||
|
||||
The short explanation is that what makes dependent type theory dependent is that types can depend on parameters.
|
||||
You have already seen a nice example of this: the type ``List α`` depends on the argument ``α``, and
|
||||
this dependence is what distinguishes ``List Nat`` and ``List Bool``.
|
||||
For another example, consider the type ``Vector α n``, the type of vectors of elements of ``α`` of length ``n``.
|
||||
This type depends on *two* parameters: the type ``α : Type`` of the elements in the vector and the length ``n : Nat``.
|
||||
|
||||
Suppose we wish to write a function ``cons`` which inserts a new element at the head of a list.
|
||||
What type should ``cons`` have? Such a function is *polymorphic*: we expect the ``cons`` function for ``Nat``, ``Bool``,
|
||||
or an arbitrary type ``α`` to behave the same way.
|
||||
So it makes sense to take the type to be the first argument to ``cons``, so that for any type, ``α``, ``cons α``
|
||||
is the insertion function for lists of type ``α``. In other words, for every ``α``, ``cons α`` is the function that takes an element ``a : α``
|
||||
and a list ``as : List α``, and returns a new list, so we have ``cons α a as : list α``.
|
||||
|
||||
It is clear that ``cons α`` should have type ``α → List α → List α``. But what type should ``cons`` have?
|
||||
A first guess might be ``Type → α → list α → list α``, but, on reflection, this does not make sense:
|
||||
the ``α`` in this expression does not refer to anything, whereas it should refer to the argument of type ``Type``.
|
||||
In other words, *assuming* ``α : Type`` is the first argument to the function, the type of the next two elements are ``α`` and ``List α``.
|
||||
These types vary depending on the first argument, ``α``.
|
||||
|
||||
This is an instance of a *dependent function type*, or *dependent arrow type*. Given ``α : Type`` and ``β : α → Type``,
|
||||
think of ``β`` as a family of types over ``α``, that is, a type ``β a`` for each ``a : α``.
|
||||
In that case, the type ``(a : α) → β a`` denotes the type of functions ``f`` with the property that,
|
||||
for each ``a : α``, ``f a`` is an element of ``β a``. In other words, the type of the value returned by ``f`` depends on its input.
|
||||
|
||||
Notice that ``(a : α) → β`` makes sense for any expression ``β : Type``. When the value of ``β`` depends on ``a``
|
||||
(as does, for example, the expression ``β a`` in the previous paragraph), ``(a : α) → β`` denotes a dependent function type.
|
||||
When ``β`` doesn't depend on ``a``, ``(a : α) → β`` is no different from the type ``α → β``.
|
||||
Indeed, in dependent type theory (and in Lean), ``α → β`` is just notation for ``(a : α) → β`` when ``β`` does not depend on ``a``.
|
||||
|
||||
Returning to the example of lists, we can use the command `#check` to inspect the type of the following `List` functions
|
||||
We will explain the ``@`` symbol and the difference between the round and curly braces momentarily.
|
||||
|
||||
```lean
|
||||
#check @List.cons -- {α : Type u_1} → α → List α → List α
|
||||
#check @List.nil -- {α : Type u_1} → List α
|
||||
#check @List.length -- {α : Type u_1} → List α → Nat
|
||||
#check @List.append -- {α : Type u_1} → List α → List α → List α
|
||||
```
|
||||
|
||||
Just as dependent function types ``(a : α) → β a`` generalize the notion of a function type ``α → β`` by allowing ``β`` to depend on ``α``,
|
||||
dependent Cartesian product types ``(a : α) × β a`` generalize the Cartesian product ``α × β`` in the same way. Dependent products are also
|
||||
called *sigma* types, and you can also write them as `Σ a : α, β a`. You can use `⟨a, b⟩` or `Sigma.mk a b` to create a dependent pair.
|
||||
|
||||
```lean
|
||||
universe u v
|
||||
|
||||
def f (α : Type u) (β : α → Type v) (a : α) (b : β a) : (a : α) × β a :=
|
||||
⟨a, b⟩
|
||||
|
||||
def g (α : Type u) (β : α → Type v) (a : α) (b : β a) : Σ a : α, β a :=
|
||||
Sigma.mk a b
|
||||
|
||||
#reduce f
|
||||
#reduce g
|
||||
|
||||
#reduce f Type (fun α => α) Nat 10
|
||||
#reduce g Type (fun α => α) Nat 10
|
||||
|
||||
#reduce (f Type (fun α => α) Nat 10).1 -- Nat
|
||||
#reduce (g Type (fun α => α) Nat 10).1 -- Nat
|
||||
#reduce (f Type (fun α => α) Nat 10).2 -- 10
|
||||
#reduce (g Type (fun α => α) Nat 10).2 -- 10
|
||||
```
|
||||
The function `f` and `g` above denote the same function.
|
||||
3
doc/deptypes.md
Normal file
3
doc/deptypes.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Dependent Types
|
||||
|
||||
In this section, we introduce simple type theory, types as objects, definitions, and explain what makes dependent type theory *dependent*.
|
||||
@@ -1,12 +1,12 @@
|
||||
# Lean Build Bootstrapping
|
||||
|
||||
Lean is a bootstrapped program: the
|
||||
Since version 4, Lean is a partially bootstrapped program: most parts of the
|
||||
frontend and compiler are written in Lean itself and thus need to be built before
|
||||
building Lean itself - which is needed to again build those parts. This cycle is
|
||||
broken by using pre-built C files checked into the repository (which ultimately
|
||||
go back to a point where the Lean compiler was not written in Lean) in place of
|
||||
these Lean inputs and then compiling everything in multiple stages up to a fixed
|
||||
point. The build directory is organized into these stages:
|
||||
point. The build directory is organized in these stages:
|
||||
|
||||
```bash
|
||||
stage0/
|
||||
@@ -72,60 +72,39 @@ update the archived C source code of the stage 0 compiler in `stage0/src`.
|
||||
|
||||
The github repository will automatically update stage0 on `master` once
|
||||
`src/stdlib_flags.h` and `stage0/src/stdlib_flags.h` are out of sync.
|
||||
To trigger this, modify `stage0/src/stdlib_flags.h` (e.g., by adding or changing
|
||||
a comment). When `update-stage0` runs, it will overwrite `stage0/src/stdlib_flags.h`
|
||||
with the contents of `src/stdlib_flags.h`, bringing them back in sync.
|
||||
|
||||
NOTE: A full rebuild of stage 1 will only be triggered when the *committed* contents of `stage0/` are changed.
|
||||
Thus if you change files in it manually instead of through `update-stage0-commit` (see below) or fetching updates from git, you either need to commit those changes first or run `make -C build/release clean-stdlib`.
|
||||
The same is true for further stages except that a rebuild of them is retriggered on any committed change, not just to a specific directory.
|
||||
Thus when debugging e.g. stage 2 failures, you can resume the build from these failures on but you may want to explicitly call `clean-stdlib` to either observe changes from `.olean` files of modules that built successfully or to check that you did not break modules that built successfully at some prior point.
|
||||
|
||||
If you have write access to the lean4 repository, you can also manually
|
||||
If you have write access to the lean4 repository, you can also also manually
|
||||
trigger that process, for example to be able to use new features in the compiler itself.
|
||||
You can do that on <https://github.com/leanprover/lean4/actions/workflows/update-stage0.yml>
|
||||
You can do that on <https://github.com/nomeata/lean4/actions/workflows/update-stage0.yml>
|
||||
or using Github CLI with
|
||||
```
|
||||
gh workflow run update-stage0.yml
|
||||
```
|
||||
|
||||
Leaving stage0 updates to the CI automation is preferable, but should you need
|
||||
to do it locally, you can use `make -C build/release update-stage0-commit` to
|
||||
update `stage0` from `stage1` or `make -C build/release/stageN update-stage0-commit` to
|
||||
update from another stage. This command will automatically stage the updated files
|
||||
and introduce a commit, so make sure to commit your work before that.
|
||||
|
||||
If you rebased the branch (either onto a newer version of `master`, or fixing
|
||||
up some commits prior to the stage0 update), recreate the stage0 update commits.
|
||||
The script `script/rebase-stage0.sh` can be used for that.
|
||||
|
||||
The CI should prevent PRs with changes to stage0 (besides `stdlib_flags.h`)
|
||||
from entering `master` through the (squashing!) merge queue, and label such PRs
|
||||
with the `changes-stage0` label. Such PRs should have a cleaned-up history,
|
||||
with separate stage0 update commits; then coordinate with the admins to merge
|
||||
your PR using rebase merge, bypassing the merge queue.
|
||||
Leaving stage0 updates to the CI automation is preferrable, but should you need
|
||||
to do it locally, you can use `make update-stage0` in `build/release`, to
|
||||
update `stage0` from `stage1`, `make -C stageN update-stage0` to update from
|
||||
another stage, or `nix run .#update-stage0-commit` to update using nix.
|
||||
|
||||
Updates to `stage0` should be their own commits in the Git history. So should
|
||||
you have to include the stage0 update in your PR (rather than using above
|
||||
automation after merging changes), commit your work before running `make
|
||||
update-stage0`, commit the updated `stage0` compiler code with the commit
|
||||
message:
|
||||
```
|
||||
chore: update stage0
|
||||
```
|
||||
and coordinate with the admins to not squash your PR.
|
||||
|
||||
## Further Bootstrapping Complications
|
||||
|
||||
As written above, changes in meta code in the current stage usually will only
|
||||
affect later stages. This is an issue in two specific cases.
|
||||
|
||||
* For the special case of *quotations*, it is desirable to have changes in builtin parsers affect them immediately: when the changes in the parser become active in the next stage, builtin macros implemented via quotations should generate syntax trees compatible with the new parser, and quotation patterns in builtin macros and elaborators should be able to match syntax created by the new parser and macros.
|
||||
Since quotations capture the syntax tree structure during execution of the current stage and turn it into code for the next stage, we need to run the current stage's builtin parsers in quotations via the interpreter for this to work.
|
||||
Caveats:
|
||||
* We activate this behavior by default when building stage 1 by setting `-Dinternal.parseQuotWithCurrentStage=true`.
|
||||
We force-disable it inside `macro/macro_rules/elab/elab_rules` via `suppressInsideQuot` as they are guaranteed not to run in the next stage and may need to be run in the current one, so the stage 0 parser is the correct one to use for them.
|
||||
It may be necessary to extend this disabling to functions that contain quotations and are (exclusively) used by one of the mentioned commands. A function using quotations should never be used by both builtin and non-builtin macros/elaborators. Example: https://github.com/leanprover/lean4/blob/f70b7e5722da6101572869d87832494e2f8534b7/src/Lean/Elab/Tactic/Config.lean#L118-L122
|
||||
* The parser needs to be reachable via an `import` statement, otherwise the version of the previous stage will silently be used.
|
||||
* Only the parser code (`Parser.fn`) is affected; all metadata such as leading tokens is taken from the previous stage.
|
||||
|
||||
For an example, see https://github.com/leanprover/lean4/commit/f9dcbbddc48ccab22c7674ba20c5f409823b4cc1#diff-371387aed38bb02bf7761084fd9460e4168ae16d1ffe5de041b47d3ad2d22422R13
|
||||
|
||||
* For *non-builtin* meta code such as `notation`s or `macro`s in
|
||||
`Notation.lean`, we expect changes to affect the current file and all later
|
||||
files of the same stage immediately, just like outside the stdlib. To ensure
|
||||
this, we build stage 1 using `-Dinterpreter.prefer_native=false` -
|
||||
this, we need to build the stage using `-Dinterpreter.prefer_native=false` -
|
||||
otherwise, when executing a macro, the interpreter would notice that there is
|
||||
already a native symbol available for this function and run it instead of the
|
||||
new IR, but the symbol is from the previous stage!
|
||||
@@ -143,11 +122,26 @@ affect later stages. This is an issue in two specific cases.
|
||||
further stages (e.g. after an `update-stage0`) will then need to be compiled
|
||||
with the flag set to `false` again since they will expect the new signature.
|
||||
|
||||
When enabling `prefer_native`, we usually want to *disable* `parseQuotWithCurrentStage` as it would otherwise make quotations use the interpreter after all.
|
||||
However, there is a specific case where we want to set both options to `true`: when we make changes to a non-builtin parser like `simp` that has a builtin elaborator, we cannot have the new parser be active outside of quotations in stage 1 as the builtin elaborator from stage 0 would not understand them; on the other hand, we need quotations in e.g. the builtin `simp` elaborator to produce the new syntax in the next stage.
|
||||
As this issue usually affects only tactics, enabling `debug.byAsSorry` instead of `prefer_native` can be a simpler solution.
|
||||
For an example, see https://github.com/leanprover/lean4/commit/da4c46370d85add64ef7ca5e7cc4638b62823fbb.
|
||||
|
||||
For a `prefer_native` example, see https://github.com/leanprover/lean4/commit/da4c46370d85add64ef7ca5e7cc4638b62823fbb.
|
||||
* For the special case of *quotations*, it is desirable to have changes in
|
||||
built-in parsers affect them immediately: when the changes in the parser
|
||||
become active in the next stage, macros implemented via quotations should
|
||||
generate syntax trees compatible with the new parser, and quotation patterns
|
||||
in macro and elaborators should be able to match syntax created by the new
|
||||
parser and macros. Since quotations capture the syntax tree structure during
|
||||
execution of the current stage and turn it into code for the next stage, we
|
||||
need to run the current stage's built-in parsers in quotation via the
|
||||
interpreter for this to work. Caveats:
|
||||
* Since interpreting full parsers is not nearly as cheap and we rarely change
|
||||
built-in syntax, this needs to be opted in using `-Dinternal.parseQuotWithCurrentStage=true`.
|
||||
* The parser needs to be reachable via an `import` statement, otherwise the
|
||||
version of the previous stage will silently be used.
|
||||
* Only the parser code (`Parser.fn`) is affected; all metadata such as leading
|
||||
tokens is taken from the previous stage.
|
||||
|
||||
For an example, see https://github.com/leanprover/lean4/commit/f9dcbbddc48ccab22c7674ba20c5f409823b4cc1#diff-371387aed38bb02bf7761084fd9460e4168ae16d1ffe5de041b47d3ad2d22422
|
||||
(from before the flag defaulted to `false`).
|
||||
|
||||
To modify either of these flags both for building and editing the stdlib, adjust
|
||||
the code in `stage0/src/stdlib_flags.h`. The flags will automatically be reset
|
||||
|
||||
@@ -33,9 +33,6 @@ Format of the commit message
|
||||
- chore (maintain, ex: travis-ci)
|
||||
- perf (performance improvement, optimization, ...)
|
||||
|
||||
Every `feat` or `fix` commit must have a `changelog-*` label, and a commit message
|
||||
beginning with "This PR " that will be included in the changelog.
|
||||
|
||||
``<subject>`` has the following constraints:
|
||||
|
||||
- use imperative, present tense: "change" not "changed" nor "changes"
|
||||
@@ -47,7 +44,6 @@ beginning with "This PR " that will be included in the changelog.
|
||||
- just as in ``<subject>``, use imperative, present tense
|
||||
- includes motivation for the change and contrasts with previous
|
||||
behavior
|
||||
- If a `changelog-*` label is present, the body must begin with "This PR ".
|
||||
|
||||
``<footer>`` is optional and may contain two items:
|
||||
|
||||
@@ -64,21 +60,17 @@ Examples
|
||||
|
||||
fix: add declarations for operator<<(std::ostream&, expr const&) and operator<<(std::ostream&, context const&) in the kernel
|
||||
|
||||
This PR adds declarations `operator<<` for raw printing.
|
||||
The actual implementation of these two operators is outside of the
|
||||
kernel. They are implemented in the file 'library/printer.cpp'.
|
||||
|
||||
We declare them in the kernel to prevent the following problem.
|
||||
Suppose there is a file 'foo.cpp' that does not include 'library/printer.h',
|
||||
kernel. They are implemented in the file 'library/printer.cpp'. We
|
||||
declare them in the kernel to prevent the following problem. Suppose
|
||||
there is a file 'foo.cpp' that does not include 'library/printer.h',
|
||||
but contains
|
||||
```cpp
|
||||
expr a;
|
||||
...
|
||||
std::cout << a << "\n";
|
||||
...
|
||||
```
|
||||
|
||||
expr a;
|
||||
...
|
||||
std::cout << a << "\n";
|
||||
...
|
||||
|
||||
The compiler does not generate an error message. It silently uses the
|
||||
operator bool() to coerce the expression into a Boolean. This produces
|
||||
counter-intuitive behavior, and may confuse developers.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Some notes on how to debug Lean, which may also be applicable to debugging Lean
|
||||
|
||||
## Tracing
|
||||
|
||||
In `CoreM` and derived monads, we use `trace[traceCls] "msg with {interpolations}"` to fill the structured trace viewable with `set_option trace.traceCls true`.
|
||||
In `CoreM` and derived monads, we use `trace![traceCls] "msg with {interpolations}"` to fill the structured trace viewable with `set_option trace.traceCls true`.
|
||||
New trace classes have to be registered using `registerTraceClass` first.
|
||||
|
||||
Notable trace classes:
|
||||
@@ -22,9 +22,7 @@ Notable trace classes:
|
||||
|
||||
In pure contexts or when execution is aborted before the messages are finally printed, one can instead use the term `dbg_trace "msg with {interpolations}"; val` (`;` can also be replaced by a newline), which will print the message to stderr before evaluating `val`. `dbgTraceVal val` can be used as a shorthand for `dbg_trace "{val}"; val`.
|
||||
Note that if the return value is not actually used, the trace code is silently dropped as well.
|
||||
|
||||
By default, such stderr output is buffered and shown as messages after a command has been elaborated, which is necessary to ensure deterministic ordering of messages under parallelism.
|
||||
If Lean aborts the process before it can finish the command or takes too long to do that, using `-DstderrAsMessages=false` avoids this buffering and shows `dbg_trace` output (but not `trace`s or other diagnostics) immediately.
|
||||
In the language server, stderr output is buffered and shown as messages after a command has been elaborated, unless the option `server.stderrAsMessages` is deactivated.
|
||||
|
||||
## Debuggers
|
||||
|
||||
|
||||
125
doc/dev/ffi.md
125
doc/dev/ffi.md
@@ -1,9 +1,124 @@
|
||||
# Foreign Function Interface
|
||||
|
||||
The Lean FFI documentation is now part of the [Lean language reference](https://lean-lang.org/doc/reference/latest/).
|
||||
NOTE: The current interface was designed for internal use in Lean and should be considered **unstable**.
|
||||
It will be refined and extended in the future.
|
||||
|
||||
* [General FFI](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=ffi)
|
||||
* [Representation of inductive types](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=inductive-types-ffi)
|
||||
* [String](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=string-ffi)
|
||||
* [Array](https://lean-lang.org/doc/reference/latest/find/?domain=Verso.Genre.Manual.section&name=array-ffi)
|
||||
As Lean is written partially in Lean itself and partially in C++, it offers efficient interoperability between the two languages (or rather, between Lean and any language supporting C interfaces).
|
||||
This support is however currently limited to transferring Lean data types; in particular, it is not possible yet to pass or return compound data structures such as C `struct`s by value from or to Lean.
|
||||
|
||||
There are two primary attributes for interoperating with other languages:
|
||||
* `@[extern "sym"] constant leanSym : ...` binds a Lean declaration to the external symbol `sym`.
|
||||
It can also be used with `def` to provide an internal definition, but ensuring consistency of both definitions is up to the user.
|
||||
* `@[export sym] def leanSym : ...` exports `leanSym` under the unmangled symbol name `sym`.
|
||||
|
||||
For simple examples of how to call foreign code from Lean and vice versa, see <https://github.com/leanprover/lean4/blob/master/src/lake/examples/ffi> and <https://github.com/leanprover/lean4/blob/master/src/lake/examples/reverse-ffi>, respectively.
|
||||
|
||||
## The Lean ABI
|
||||
|
||||
The Lean Application Binary Interface (ABI) describes how the signature of a Lean declaration is encoded as a native calling convention.
|
||||
It is based on the standard C ABI and calling convention of the target platform.
|
||||
For a Lean declaration marked with either `@[extern "sym"]` or `@[export sym]` for some symbol name `sym`, let `α₁ → ... → αₙ → β` be the normalized declaration's type.
|
||||
If `n` is 0, the corresponding C declaration is
|
||||
```c
|
||||
extern s sym;
|
||||
```
|
||||
where `s` is the C translation of `β` as specified in the next section.
|
||||
In the case of an `@[extern]` definition, the symbol's value is guaranteed to be initialized only after calling the Lean module's initializer or that of an importing module; see [Initialization](#initialization).
|
||||
|
||||
If `n` is greater than 0, the corresponding C declaration is
|
||||
```c
|
||||
s sym(t₁, ..., tₘ);
|
||||
```
|
||||
where the parameter types `tᵢ` are the C translation of the `αᵢ` as in the next section.
|
||||
In the case of `@[extern]` all *irrelevant* types are removed first; see next section.
|
||||
|
||||
### Translating Types from Lean to C
|
||||
|
||||
* The integer types `UInt8`, ..., `UInt64`, `USize` are represented by the C types `uint8_t`, ..., `uint64_t`, `size_t`, respectively
|
||||
* `Char` is represented by `uint32_t`
|
||||
* `Float` is represented by `double`
|
||||
* An *enum* inductive type of at least 2 and at most 2^32 constructors, each of which with no parameters, is represented by the first type of `uint8_t`, `uint16_t`, `uint32_t` that is sufficient to represent all constructor indices.
|
||||
|
||||
For example, the type `Bool` is represented as `uint8_t` with values `0` for `false` and `1` for `true`.
|
||||
* `Decidable α` is represented the same way as `Bool`
|
||||
* An inductive type with a *trivial structure*, that is,
|
||||
* it is none of the types described above
|
||||
* it is not marked `unsafe`
|
||||
* it has a single constructor with a single parameter of *relevant* type
|
||||
|
||||
is represented by the representation of that parameter's type.
|
||||
|
||||
For example, `{ x : α // p }`, the `Subtype` structure of a value of type `α` and an irrelevant proof, is represented by the representation of `α`.
|
||||
* `Nat` is represented by `lean_object *`.
|
||||
Its runtime value is either a pointer to an opaque bignum object or, if the lowest bit of the "pointer" is 1 (`lean_is_scalar`), an encoded unboxed natural number (`lean_box`/`lean_unbox`).
|
||||
* A universe `Sort u`, type constructor `... → Sort u`, or proposition `p : Prop` is *irrelevant* and is either statically erased (see above) or represented as a `lean_object *` with the runtime value `lean_box(0)`
|
||||
* Any other type is represented by `lean_object *`.
|
||||
Its runtime value is a pointer to an object of a subtype of `lean_object` (see respective declarations in `lean.h`) or the unboxed value `lean_box(cidx)` for the `cidx`th constructor of an inductive type if this constructor does not have any relevant parameters.
|
||||
|
||||
Example: the runtime value of `u : Unit` is always `lean_box(0)`.
|
||||
|
||||
### Borrowing
|
||||
|
||||
By default, all `lean_object *` parameters of an `@[extern]` function are considered *owned*, i.e. the external code is passed a "virtual RC token" and is responsible for passing this token along to another consuming function (exactly once) or freeing it via `lean_dec`.
|
||||
To reduce reference counting overhead, parameters can be marked as *borrowed* by prefixing their type with `@&`.
|
||||
Borrowed objects must only be passed to other non-consuming functions (arbitrarily often) or converted to owned values using `lean_inc`.
|
||||
In `lean.h`, the `lean_object *` aliases `lean_obj_arg` and `b_lean_obj_arg` are used to mark this difference on the C side.
|
||||
|
||||
Return values and `@[export]` parameters are always owned at the moment.
|
||||
|
||||
## Initialization
|
||||
|
||||
When including Lean code as part of a larger program, modules must be *initialized* before accessing any of their declarations.
|
||||
Module initialization entails
|
||||
* initialization of all "constants" (nullary functions), including closed terms lifted out of other functions
|
||||
* execution of all `[init]` functions
|
||||
* execution of all `[builtin_init]` functions, if the `builtin` parameter of the module initializer has been set
|
||||
|
||||
The module initializer is automatically run with the `builtin` flag for executables compiled from Lean code and for "plugins" loaded with `lean --plugin`.
|
||||
For all other modules imported by `lean`, the initializer is run without `builtin`.
|
||||
Thus `[init]` functions are run iff their module is imported, regardless of whether they have native code available or not, while `[builtin_init]` functions are only run for native executable or plugins, regardless of whether their module is imported or not.
|
||||
`lean` uses built-in initializers for e.g. registering basic parsers that should be available even without importing their module (which is necessary for bootstrapping).
|
||||
|
||||
The initializer for module `A.B` is called `initialize_A_B` and will automatically initialize any imported modules.
|
||||
Module initializers are idempotent (when run with the same `builtin` flag), but not thread-safe.
|
||||
Together with initialization of the Lean runtime, you should execute code like the following exactly once before accessing any Lean declarations:
|
||||
```c
|
||||
void lean_initialize_runtime_module();
|
||||
void lean_initialize();
|
||||
lean_object * initialize_A_B(uint8_t builtin, lean_object *);
|
||||
lean_object * initialize_C(uint8_t builtin, lean_object *);
|
||||
...
|
||||
|
||||
lean_initialize_runtime_module();
|
||||
//lean_initialize(); // necessary if you (indirectly) access the `Lean` package
|
||||
|
||||
lean_object * res;
|
||||
// use same default as for Lean executables
|
||||
uint8_t builtin = 1;
|
||||
res = initialize_A_B(builtin, lean_io_mk_world());
|
||||
if (lean_io_result_is_ok(res)) {
|
||||
lean_dec_ref(res);
|
||||
} else {
|
||||
lean_io_result_show_error(res);
|
||||
lean_dec(res);
|
||||
return ...; // do not access Lean declarations if initialization failed
|
||||
}
|
||||
res = initialize_C(builtin, lean_io_mk_world());
|
||||
if (lean_io_result_is_ok(res)) {
|
||||
...
|
||||
|
||||
//lean_init_task_manager(); // necessary if you (indirectly) use `Task`
|
||||
lean_io_mark_end_initialization();
|
||||
```
|
||||
|
||||
## `@[extern]` in the Interpreter
|
||||
|
||||
The interpreter can run Lean declarations for which symbols are available in loaded shared libraries, which includes `@[extern]` declarations.
|
||||
Thus to e.g. run `#eval` on such a declaration, you need to
|
||||
1. compile (at least) the module containing the declaration and its dependencies into a shared library, and then
|
||||
1. pass this library to `lean --load-dynlib=` to run code `import`ing this module.
|
||||
|
||||
Note that it is not sufficient to load the foreign library containing the external symbol because the interpreter depends on code that is emitted for each `@[extern]` declaration.
|
||||
Thus it is not possible to interpret an `@[extern]` declaration in the same file.
|
||||
|
||||
See [`tests/compiler/foreign`](https://github.com/leanprover/lean4/tree/master/tests/compiler/foreign/) for an example.
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
# Development Workflow
|
||||
|
||||
If you want to make changes to Lean itself, start by [building Lean](../make/index.md) from a clean checkout to make sure that everything is set up correctly.
|
||||
After that, read on below to find out how to set up your editor for changing the Lean source code,
|
||||
followed by further sections of the development manual where applicable
|
||||
such as on the [test suite](../../tests/README.md) and [commit convention](commit_convention.md).
|
||||
After that, read on below to find out how to set up your editor for changing the Lean source code, followed by further sections of the development manual where applicable such as on the [test suite](testing.md) and [commit convention](commit_convention.md).
|
||||
|
||||
If you are planning to make any changes that may affect the compilation of Lean itself, e.g. changes to the parser, elaborator, or compiler, you should first read about the [bootstrapping pipeline](bootstrap.md).
|
||||
You should not edit the `stage0` directory except using the commands described in that section when necessary.
|
||||
|
||||
## Development Setup
|
||||
|
||||
You can use any of the [supported editors](https://lean-lang.org/install/manual/) for editing the Lean source code.
|
||||
Please see below for specific instructions for VS Code.
|
||||
You can use any of the [supported editors](../setup.md) for editing the Lean source code.
|
||||
If you set up `elan` as below, opening `src/` as a *workspace folder* should ensure that stage 0 (i.e. the stage that first compiles `src/`) will be used for files in that directory.
|
||||
|
||||
### Dev setup using elan
|
||||
|
||||
@@ -63,57 +61,16 @@ you can then put `my_name/lean4:my-tag` in your `lean-toolchain` file in a proje
|
||||
|
||||
### VS Code
|
||||
|
||||
There is a `.vscode/` directory that correctly sets up VS Code with settings, tasks, and recommended extensions.
|
||||
Simply open the repository folder in VS Code, such as by invoking
|
||||
There is a `lean.code-workspace` file that correctly sets up VS Code with workspace roots for the stage0/stage1 setup described above as well as with other settings.
|
||||
You should always load it when working on Lean, such as by invoking
|
||||
```
|
||||
code .
|
||||
code lean.code-workspace
|
||||
```
|
||||
on the command line.
|
||||
|
||||
You can use the `Refresh File Dependencies` command as in other projects to rebuild modules from inside VS Code but be aware that this does not trigger any non-Lake build targets.
|
||||
In particular, after updating `stage0/` (or fetching an update to it), you will want to invoke `make` directly to rebuild `stage0/bin/lean` as described in [building Lean](../make/index.md).
|
||||
You should then run the `Restart Server` command to update all open files and the server watchdog process as well.
|
||||
|
||||
### `ccache`
|
||||
|
||||
Lean's build process uses [`ccache`](https://ccache.dev/) if it is
|
||||
installed to speed up recompilation of the generated C code. Without
|
||||
`ccache`, you'll likely spend more time than necessary waiting on
|
||||
rebuilds - it's a good idea to make sure it's installed.
|
||||
|
||||
### `prelude`
|
||||
Unlike most Lean projects, all submodules of the `Lean` module begin with the
|
||||
`prelude` keyword. This disables the automated import of `Init`, meaning that
|
||||
developers need to figure out their own subset of `Init` to import. This is done
|
||||
such that changing files in `Init` doesn't force a full rebuild of `Lean`.
|
||||
|
||||
### Testing against Mathlib/Batteries
|
||||
You can test a Lean PR against Mathlib and Batteries by rebasing your PR
|
||||
on to `nightly-with-mathlib` branch. (It is fine to force push after rebasing.)
|
||||
CI will generate a branch of Mathlib and Batteries called `lean-pr-testing-NNNN`
|
||||
on the `leanprover-community/mathlib4-nightly-testing` fork of Mathlib.
|
||||
This branch uses the toolchain for your PR, and will report back to the Lean PR with results from Mathlib CI.
|
||||
See https://leanprover-community.github.io/contribute/tags_and_branches.html for more details.
|
||||
|
||||
### Testing against the Lean Language Reference
|
||||
You can test a Lean PR against the reference manual by rebasing your PR
|
||||
on to `nightly-with-manual` branch. (It is fine to force push after rebasing.)
|
||||
CI will generate a branch of the reference manual called `lean-pr-testing-NNNN`
|
||||
in `leanprover/reference-manual`. This branch uses the toolchain for your PR,
|
||||
and will report back to the Lean PR with results from Mathlib CI.
|
||||
|
||||
### Avoiding rebuilds for downstream projects
|
||||
|
||||
If you want to test changes to Lean on downstream projects and would like to avoid rebuilding modules you have already built/fetched using the project's configured Lean toolchain, you can often do so as long as your build of Lean is close enough to that Lean toolchain (compatible .olean format including structure of all relevant environment extensions).
|
||||
|
||||
To override the toolchain without rebuilding for a single command, for example `lake build` or `lake lean`, you can use the prefix
|
||||
```
|
||||
LEAN_GITHASH=$(lean --githash) lake +lean4 ...
|
||||
```
|
||||
Alternatively, use
|
||||
```
|
||||
export LEAN_GITHASH=$(lean --githash)
|
||||
export ELAN_TOOLCHAIN=lean4
|
||||
```
|
||||
to persist these changes for the lifetime of the current shell, which will affect any processes spawned from it such as VS Code started via `code .`.
|
||||
If you use a setup where you cannot directly start your editor from the command line, such as VS Code Remote, you might want to consider using [direnv](https://direnv.net/) together with an editor extension for it instead so that you can put the lines above into `.envrc`.
|
||||
|
||||
109
doc/dev/mdbook.md
Normal file
109
doc/dev/mdbook.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Documentation
|
||||
|
||||
The Lean `doc` folder contains the [Lean Manual](https://lean-lang.org/lean4/doc/) and is
|
||||
authored in a combination of markdown (`*.md`) files and literate Lean files. The .lean files are
|
||||
preprocessed using a tool called [LeanInk](https://github.com/leanprover/leanink) and
|
||||
[Alectryon](https://github.com/Kha/alectryon) which produces a generated markdown file. We then run
|
||||
`mdbook` on the result to generate the html pages.
|
||||
|
||||
|
||||
## Settings
|
||||
|
||||
We are using the following settings while editing the markdown docs.
|
||||
|
||||
```json
|
||||
{
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"[markdown]": {
|
||||
"rewrap.wrappingColumn": 70
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Build
|
||||
|
||||
### Using Nix
|
||||
|
||||
Building the manual using Nix (which is what the CI does) is as easy as
|
||||
```bash
|
||||
$ nix build --update-input lean ./doc
|
||||
```
|
||||
You can also open a shell with `mdbook` for running the commands mentioned below with
|
||||
`nix develop ./doc#book`. Otherwise, read on.
|
||||
|
||||
### Manually
|
||||
|
||||
To build and test the book you have to preprocess the .lean files with Alectryon then use our own
|
||||
fork of the Rust tool named [mdbook](https://github.com/leanprover/mdbook). We have our own fork of
|
||||
mdBook with the following additional features:
|
||||
|
||||
* Add support for hiding lines in other languages
|
||||
[#1339](https://github.com/rust-lang/mdBook/pull/1339)
|
||||
* Make `mdbook test` call the `lean` compiler to test the snippets.
|
||||
* Ability to test a single chapter at a time which is handy when you
|
||||
are working on that chapter. See the `--chapter` option.
|
||||
|
||||
So you need to setup these tools before you can run `mdBook`.
|
||||
|
||||
1. install [Rust](https://www.rust-lang.org/tools/install)
|
||||
which provides you with the `cargo` tool for building rust packages.
|
||||
Then run the following:
|
||||
```bash
|
||||
cargo install --git https://github.com/leanprover/mdBook mdbook
|
||||
```
|
||||
|
||||
1. Clone https://github.com/leanprover/LeanInk.git and run `lake build` then make the resulting
|
||||
binary available to Alectryon using e.g.
|
||||
```bash
|
||||
# make `leanInk` available in the current shell
|
||||
export PATH=$PWD/build/bin:$PATH
|
||||
```
|
||||
|
||||
1. Create a Python 3.10 environment.
|
||||
|
||||
1. Install Alectryon:
|
||||
```
|
||||
python3 -m pip install git+https://github.com/Kha/alectryon.git@typeid
|
||||
```
|
||||
|
||||
1. Now you are ready to process the `*.lean` files using Alectryon as follows:
|
||||
|
||||
```
|
||||
cd lean4/doc
|
||||
alectryon --frontend lean4+markup examples/palindromes.lean --backend webpage -o palindromes.lean.md
|
||||
```
|
||||
|
||||
Repeat this for the other .lean files you care about or write a script to process them all.
|
||||
|
||||
1. Now you can build the book using:
|
||||
```
|
||||
cd lean4/doc
|
||||
mdbook build
|
||||
```
|
||||
|
||||
This will put the HTML in a `out` folder so you can load `out/index.html` in your web browser and
|
||||
it should look like https://lean-lang.org/lean4/doc/.
|
||||
|
||||
1. It is also handy to use e.g. [`mdbook watch`](https://rust-lang.github.io/mdBook/cli/watch.html)
|
||||
in the `doc/` folder so that it keeps the html up to date while you are editing.
|
||||
|
||||
```bash
|
||||
mdbook watch --open # opens the output in `out/` in your default browser
|
||||
```
|
||||
|
||||
## Testing Lean Snippets
|
||||
|
||||
You can run the following in the `doc/` folder to test all the lean code snippets.
|
||||
|
||||
```bash
|
||||
mdbook test
|
||||
```
|
||||
|
||||
and you can use the `--chapter` option to test a specific chapter that you are working on:
|
||||
|
||||
```bash
|
||||
mdbook test --chapter Array
|
||||
```
|
||||
|
||||
Use chapter name `?` to get a list of all the chapter names.
|
||||
@@ -1,369 +0,0 @@
|
||||
# Releasing a stable version
|
||||
|
||||
This checklist walks you through releasing a stable version.
|
||||
See below for the checklist for release candidates.
|
||||
|
||||
We'll use `v4.6.0` as the intended release version as a running example.
|
||||
|
||||
- Run `script/release_checklist.py v4.6.0` to check the status of the release.
|
||||
This script is idempotent, and should be safe to run at any stage of the release process.
|
||||
Note that as of v4.19.0, this script takes some autonomous actions, which can be prevented via `--dry-run`.
|
||||
- `git checkout releases/v4.6.0`
|
||||
(This branch should already exist, from the release candidates.)
|
||||
- `git pull`
|
||||
- In `src/CMakeLists.txt`, verify you see
|
||||
- `set(LEAN_VERSION_MINOR 6)` (for whichever `6` is appropriate)
|
||||
- `set(LEAN_VERSION_IS_RELEASE 1)`
|
||||
- (all of these should already be in place from the release candidates)
|
||||
- `git tag v4.6.0`
|
||||
- `git push $REMOTE v4.6.0`, where `$REMOTE` is the upstream Lean repository (e.g., `origin`, `upstream`)
|
||||
- Now wait, while CI runs.
|
||||
- You can monitor this at `https://github.com/leanprover/lean4/actions/workflows/ci.yml`,
|
||||
looking for the `v4.6.0` tag.
|
||||
- This step can take up to two hours.
|
||||
- If you are intending to cut the next release candidate on the same day,
|
||||
you may want to start on the release candidate checklist now.
|
||||
- Next we need to prepare the release notes.
|
||||
- If the stable release is identical to the last release candidate (this should usually be the case),
|
||||
you can reuse the release notes that are already in the Lean Language Reference.
|
||||
- If you want to regenerate the release notes,
|
||||
run `script/release_notes.py --since v4.5.0` on the `releases/v4.6.0` branch,
|
||||
and see the section "Writing the release notes" below for more information.
|
||||
- Release notes live in https://github.com/leanprover/reference-manual, in e.g. `Manual/Releases/v4.6.0.lean`.
|
||||
It's best if you update these at the same time as you update the `lean-toolchain` for the `reference-manual` repository, see below.
|
||||
- Go to https://github.com/leanprover/lean4/releases and verify that the `v4.6.0` release appears.
|
||||
- Verify on Github that "Set as the latest release" is checked.
|
||||
- Next, we will move a curated list of downstream repos to the latest stable release.
|
||||
- In order to have the access rights to push to these repositories and merge PRs,
|
||||
you will need to be a member of the `lean-release-managers` team at both `leanprover-community` and `leanprover`.
|
||||
Contact Kim Morrison (@kim-em) to arrange access.
|
||||
- For each of the repositories listed in `script/release_repos.yml`,
|
||||
- Run `script/release_steps.py v4.6.0 <repo>` (e.g. replacing `<repo>` with `batteries`), which will walk you through the following steps:
|
||||
- Create a new branch off `master`/`main` (as specified in the `branch` field), called `bump_to_v4.6.0`.
|
||||
- Update the contents of `lean-toolchain` to `leanprover/lean4:v4.6.0`.
|
||||
- In the `lakefile.toml` or `lakefile.lean`, if there are dependencies on specific version tags of dependencies, update them to the new tag.
|
||||
If they depend on `main` or `master`, don't change this; you've just updated the dependency, so `lake update` will take care of modifying the manifest.
|
||||
- Run `lake update`
|
||||
- Commit the changes as `chore: bump toolchain to v4.6.0` and push.
|
||||
- Create a PR with title "chore: bump toolchain to v4.6.0".
|
||||
- Merge the PR once CI completes.
|
||||
- Re-running `script/release_checklist.py` will then create the tag `v4.6.0` from `master`/`main` and push it (unless `toolchain-tag: false` in the `release_repos.yml` file)
|
||||
- `script/release_checklist.py` will then merge the tag `v4.6.0` into the `stable` branch and push it (unless `stable-branch: false` in the `release_repos.yml` file).
|
||||
- Special notes on repositories with exceptional requirements:
|
||||
- `doc-gen4` has additional dependencies which we do not update at each toolchain release, although occasionally these break and need to be updated manually.
|
||||
- `verso`:
|
||||
- The `subverso` dependency is unusual in that it needs to be compatible with _every_ Lean release simultaneously.
|
||||
Usually you don't need to do anything.
|
||||
If you think something is wrong here, please contact David Thrane Christiansen (@david-christiansen)
|
||||
- Warnings during `lake update` and `lake build` are expected.
|
||||
- `reference-manual`: the release notes generated by `script/release_notes.py` as described above must be included in
|
||||
`Manual/Releases/v4.6.0.lean`, and `import` and `include` statements adding in `Manual/Releases.lean`.
|
||||
- `ProofWidgets4` uses a non-standard sequential version tagging scheme, e.g. `v0.0.29`, which does not refer to the toolchain being used.
|
||||
You will need to identify the next available version number from https://github.com/leanprover-community/ProofWidgets4/releases,
|
||||
and push a new tag after merging the PR to `main`.
|
||||
- `mathlib4`:
|
||||
- The `lakefile.toml` should always refer to dependencies via their `main` or `master` branch,
|
||||
not a toolchain tag
|
||||
(with the exception of `ProofWidgets4`, which *must* use a sequential version tag).
|
||||
- **Important:** After creating and pushing the ProofWidgets4 tag (see above),
|
||||
the mathlib4 lakefile must be updated to reference the new tag (e.g. `v0.0.87`).
|
||||
The `release_steps.py` script handles this automatically by looking up the latest
|
||||
ProofWidgets4 tag compatible with the target toolchain.
|
||||
- Push the PR branch to the main Mathlib repository rather than a fork, or CI may not work reliably
|
||||
- The "Verify Transient and Automated Commits" CI check on toolchain bump PRs can be ignored —
|
||||
it often fails on automated commits (`x:` prefixed) from the nightly-testing history that can't be
|
||||
reproduced in CI. This does not block merging.
|
||||
- `repl`:
|
||||
There are two copies of `lean-toolchain`/`lakefile.lean`:
|
||||
in the root, and in `test/Mathlib/`. Edit both, and run `lake update` in both directories.
|
||||
- `lean-fro.org`:
|
||||
After updating the toolchains and running `lake update`, you must run `scripts/update.sh` to regenerate
|
||||
the site content. This script updates generated files that depend on the Lean version.
|
||||
The `release_steps.py` script handles this automatically.
|
||||
- An awkward situation that sometimes occurs (e.g. with Verso) is that the `master`/`main` branch has already been moved
|
||||
to a nightly toolchain that comes *after* the stable toolchain we are
|
||||
targeting. In this case it is necessary to create a branch `releases/v4.6.0` from the last commit which was on
|
||||
an earlier toolchain, move that branch to the stable toolchain, and create the toolchain tag from that branch.
|
||||
- Run `script/release_checklist.py v4.6.0` one last time to check that everything is in order.
|
||||
- Finally, make an announcement!
|
||||
This should go in https://leanprover.zulipchat.com/#narrow/stream/113486-announce, with topic `v4.6.0`.
|
||||
Please see previous announcements for suggested language.
|
||||
You will want a few bullet points for main topics from the release notes.
|
||||
If there is a blog post, link to that from the zulip announcement.
|
||||
- Make sure that whoever is handling social media knows the release is out.
|
||||
|
||||
## Time estimates:
|
||||
- Initial checks and push the tag: 10 minutes.
|
||||
- Waiting for the release: 120 minutes.
|
||||
- Preparing release notes: 10 minutes.
|
||||
- Bumping toolchains in downstream repositories, up to creating the Mathlib PR: 60 minutes.
|
||||
- Waiting for Mathlib CI and bors: 120 minutes.
|
||||
- Finalizing Mathlib tags and stable branch, and updating REPL: 20 minutes.
|
||||
- Posting announcement and/or blog post: 30 minutes.
|
||||
|
||||
# Creating a release candidate.
|
||||
|
||||
This checklist walks you through creating the first release candidate for a version of Lean.
|
||||
|
||||
For subsequent release candidates, the process is essentially the same, but we start out with the `releases/v4.7.0` branch already created.
|
||||
|
||||
We'll use `v4.7.0-rc1` as the intended release version in this example.
|
||||
|
||||
- Decide which nightly release you want to turn into a release candidate.
|
||||
We will use `nightly-2024-02-29` in this example.
|
||||
- It is essential to choose the nightly that will become the release candidate as early as possible, to avoid confusion.
|
||||
- Throughout this process you can use `script/release_checklist.py v4.7.0-rc1` to track progress.
|
||||
This script will also try to do some steps autonomously. It is idempotent and safe to run at any point.
|
||||
You can prevent it taking any actions using `--dry-run`.
|
||||
- It is essential that Batteries and Mathlib already have reviewed branches compatible with this nightly.
|
||||
- Check that both Batteries and Mathlib's `bump/v4.7.0` branch contain `nightly-2024-02-29`
|
||||
in their `lean-toolchain`.
|
||||
- The steps required to reach that state are beyond the scope of this checklist, but see below!
|
||||
- Create the release branch from this nightly tag:
|
||||
```
|
||||
git remote add nightly https://github.com/leanprover/lean4-nightly.git
|
||||
git fetch nightly tag nightly-2024-02-29
|
||||
git checkout nightly-2024-02-29
|
||||
git checkout -b releases/v4.7.0
|
||||
git push --set-upstream origin releases/v4.7.0
|
||||
```
|
||||
- In `src/CMakeLists.txt`,
|
||||
- verify that you see `set(LEAN_VERSION_MINOR 7)` (for whichever `7` is appropriate); this should already have been updated when the development cycle began.
|
||||
- change the `LEAN_VERSION_IS_RELEASE` line to `set(LEAN_VERSION_IS_RELEASE 1)` (this should be a change; on `master` and nightly releases it is always `0`).
|
||||
- Commit your changes to `src/CMakeLists.txt`, and push.
|
||||
- `git tag v4.7.0-rc1`
|
||||
- `git push origin v4.7.0-rc1`
|
||||
- Now wait, while CI runs.
|
||||
- The CI setup parses the tag to discover the `-rc1` special description, and passes it to `cmake` using a `-D` option. The `-rc1` doesn't need to be placed in the configuration file.
|
||||
- You can monitor this at `https://github.com/leanprover/lean4/actions/workflows/ci.yml`, looking for the `v4.7.0-rc1` tag.
|
||||
- This step can take up to two hours.
|
||||
- Verify that the release appears at https://github.com/leanprover/lean4/releases/, marked as a prerelease (this should have been done automatically by the CI release job).
|
||||
- Next we need to prepare the release notes.
|
||||
- Run `script/release_notes.py --since v4.6.0` on the `releases/v4.7.0` branch,
|
||||
which will report diagnostic messages on `stderr`
|
||||
(including reporting commits that it couldn't associate with a PR, and hence will be omitted)
|
||||
and then a chunk of markdown on `stdout`.
|
||||
See the section "Writing the release notes" below for more information.
|
||||
- Release notes live in https://github.com/leanprover/reference-manual, in e.g. `Manual/Releases/v4.7.0.lean`.
|
||||
It's best if you update these at the same time as a you update the `lean-toolchain` for the `reference-manual` repository, see below.
|
||||
- Next, we will move a curated list of downstream repos to the release candidate.
|
||||
- This assumes that for each repository either:
|
||||
* There is already a *reviewed* branch `bump/v4.7.0` containing the required adaptations.
|
||||
The preparation of this branch is beyond the scope of this document.
|
||||
* The repository does not need any changes to move to the new version.
|
||||
* Note that sometimes there are *unreviewed* but necessary changes on the `nightly-testing` branch of the repository.
|
||||
If so, you will need to merge these into the `bump_to_v4.7.0-rc1` branch manually.
|
||||
* The `nightly-testing` branch may also contain temporary fix scripts (e.g. `fix_backward_defeq.py`,
|
||||
`fix_deprecations.py`) that were used to adapt to breaking changes during the nightly cycle.
|
||||
These should be reviewed and removed if no longer needed, as they can interfere with CI checks.
|
||||
- For each of the repositories listed in `script/release_repos.yml`,
|
||||
- Run `script/release_steps.py v4.7.0-rc1 <repo>` (e.g. replacing `<repo>` with `batteries`), which will walk you through the following steps:
|
||||
- Create a new branch off `master`/`main` (as specified in the `branch` field), called `bump_to_v4.7.0-rc1`.
|
||||
- Merge `origin/bump/v4.7.0` if relevant (i.e. `bump-branch: true` appears in `release_repos.yml`).
|
||||
- Otherwise, you *may* need to merge `origin/nightly-testing`.
|
||||
- Note that for `verso` and `reference-manual` development happens on `nightly-testing`, so
|
||||
we will merge that branch into `bump_to_v4.7.0-rc1`, but it is essential in the GitHub interface that we do a rebase merge,
|
||||
in order to preserve the history.
|
||||
- Update the contents of `lean-toolchain` to `leanprover/lean4:v4.7.0-rc1`.
|
||||
- In the `lakefile.toml` or `lakefile.lean`, if there are dependencies on `nightly-testing`, `bump/v4.7.0`, or specific version tags, update them to the new tag.
|
||||
If they depend on `main` or `master`, don't change this; you've just updated the dependency, so `lake update` will take care of modifying the manifest.
|
||||
- Run `lake update`
|
||||
- Run `lake build && if lake check-test; then lake test; fi` to check things are working.
|
||||
- Commit the changes as `chore: bump toolchain to v4.7.0-rc1` and push.
|
||||
- Create a PR with title "chore: bump toolchain to v4.7.0-rc1".
|
||||
- Merge the PR once CI completes. (Recall: for `verso` and `reference-manual` you will need to do a rebase merge.)
|
||||
- Re-running `script/release_checklist.py` will then create the tag `v4.7.0-rc1` from `master`/`main` and push it (unless `toolchain-tag: false` in the `release_repos.yml` file)
|
||||
- We do this for the same list of repositories as for stable releases, see above for notes about special cases.
|
||||
As above, there are dependencies between these, and so the process above is iterative.
|
||||
It greatly helps if you can merge the `bump/v4.7.0` PRs yourself!
|
||||
- It is essential for Mathlib and Batteries CI that you then create the next `bump/v4.8.0` branch
|
||||
for the next development cycle.
|
||||
Set the `lean-toolchain` file on this branch to same `nightly` you used for this release.
|
||||
- Run `script/release_checklist.py v4.7.0-rc1` one last time to check that everything is in order.
|
||||
- Make an announcement!
|
||||
This should go in https://leanprover.zulipchat.com/#narrow/stream/113486-announce, with topic `v4.7.0-rc1`.
|
||||
Please see previous announcements for suggested language.
|
||||
You will want a few bullet points for main topics from the release notes.
|
||||
Please also make sure that whoever is handling social media knows the release is out.
|
||||
- Begin the next development cycle (i.e. for `v4.8.0`) on the Lean repository, by making a PR that:
|
||||
- Uses branch name `dev_cycle_v4.8`.
|
||||
- Updates `src/CMakeLists.txt` to say `set(LEAN_VERSION_MINOR 8)`
|
||||
- Titled "chore: begin development cycle for v4.8.0"
|
||||
|
||||
## Time estimates:
|
||||
Slightly longer than the corresponding steps for a stable release.
|
||||
Similar process, but more things go wrong.
|
||||
In particular, updating the downstream repositories is significantly more work
|
||||
(because we need to merge existing `bump/v4.7.0` branches, not just update a toolchain).
|
||||
|
||||
# Preparing `bump/v4.7.0` branches
|
||||
|
||||
While not part of the release process per se,
|
||||
this is a brief summary of the work that goes into updating Batteries/Aesop/Mathlib to new versions.
|
||||
|
||||
Please read https://leanprover-community.github.io/contribute/tags_and_branches.html
|
||||
|
||||
* Each repo has an unreviewed `nightly-testing` branch that
|
||||
receives commits automatically from `master`, and
|
||||
has its toolchain updated automatically for every nightly.
|
||||
(Note: the aesop branch is not automated, and is updated on an as needed basis.)
|
||||
As a consequence this branch is often broken.
|
||||
A bot posts in the (private!) "Mathlib reviewers" stream on Zulip about the status of these branches.
|
||||
* We fix the breakages by committing directly to `nightly-testing`: there is no PR process.
|
||||
* This can either be done by the person managing this process directly,
|
||||
or by soliciting assistance from authors of files, or generally helpful people on Zulip!
|
||||
* Each repo has a `bump/v4.7.0` which accumulates reviewed changes adapting to new versions.
|
||||
* Once `nightly-testing` is working on a given nightly, say `nightly-2024-02-15`, we will create a PR to `bump/v4.7.0`.
|
||||
* For Mathlib, there is a script in `scripts/create-adaptation-pr.sh` that automates this process.
|
||||
* For Batteries and Aesop it is currently manual.
|
||||
* For all of these repositories, the process is the same:
|
||||
* Make sure `bump/v4.7.0` is up to date with `master` (by merging `master`, no PR necessary)
|
||||
* Create from `bump/v4.7.0` a `bump/nightly-2024-02-15` branch.
|
||||
* In that branch, `git merge nightly-testing` to bring across changes from `nightly-testing`.
|
||||
* Sanity check changes, commit, and make a PR to `bump/v4.7.0` from the `bump/nightly-2024-02-15` branch.
|
||||
* Solicit review, merge the PR into `bump/v4.7.0`.
|
||||
* It is always okay to merge in the following directions:
|
||||
`master` -> `bump/v4.7.0` -> `bump/nightly-2024-02-15` -> `nightly-testing`.
|
||||
Please remember to push any merges you make to intermediate steps!
|
||||
|
||||
# Writing the release notes
|
||||
|
||||
Release notes content is only written for the first release candidate (`-rc1`). For subsequent RCs and stable releases,
|
||||
just update the title in the existing release notes file (see "Release notes title format" below).
|
||||
|
||||
## Release notes title format
|
||||
|
||||
The title in the `#doc (Manual)` line must follow these formats:
|
||||
|
||||
- **For -rc1**: `"Lean 4.7.0-rc1 (2024-03-15)"` — Include the RC suffix and the release date
|
||||
- **For -rc2, -rc3, etc.**: `"Lean 4.7.0-rc2 (2024-03-20)"` — Update the RC number and date
|
||||
- **For stable release**: `"Lean 4.7.0 (2024-04-01)"` — Remove the RC suffix but keep the date
|
||||
|
||||
The date should be the actual date when the tag was pushed (or when CI completed and created the release page).
|
||||
|
||||
## 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.
|
||||
This script should be run on the `releases/v4.7.0` branch.
|
||||
This will generate output for all commits since that tag.
|
||||
Note that there is output on both stderr, which should be manually reviewed,
|
||||
and on stdout, which should be manually copied into the `reference-manual` repository, in the file `Manual/Releases/v4.7.0.lean`.
|
||||
|
||||
The output on stderr should mostly be about commits for which the script could not find an associated PR,
|
||||
usually because a PR was rebase-merged because it contained an update to stage0.
|
||||
Some judgement is required here: ignore commits which look minor,
|
||||
but manually add items to the release notes for significant PRs that were rebase-merged.
|
||||
|
||||
There can also be pre-written entries in `./releases_drafts`, which should be all incorporated in the release notes and then deleted from the branch.
|
||||
|
||||
## 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 (2024-03-15)" =>
|
||||
%%%
|
||||
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.
|
||||
134
doc/dev/testing.md
Normal file
134
doc/dev/testing.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# Test Suite
|
||||
|
||||
After [building Lean](../make/index.md) you can run all the tests using
|
||||
```
|
||||
cd build/release
|
||||
make test ARGS=-j4
|
||||
```
|
||||
Change the 4 to the maximum number of parallel tests you want to
|
||||
allow. The best choice is the number of CPU cores on your machine as
|
||||
the tests are mostly CPU bound. You can find the number of processors
|
||||
on linux using `nproc` and on Windows it is the `NUMBER_OF_PROCESSORS`
|
||||
environment variable.
|
||||
|
||||
You can run tests after [building a specific stage](bootstrap.md) by
|
||||
adding the `-C stageN` argument. The default when run as above is stage 1. The
|
||||
Lean tests will automatically use that stage's corresponding Lean
|
||||
executables
|
||||
|
||||
Running `make test` will not pick up new test files; run
|
||||
```bash
|
||||
cmake build/release/stage1
|
||||
```
|
||||
to update the list of tests.
|
||||
|
||||
You can also use `ctest` directly if you are in the right folder. So
|
||||
to run stage1 tests with a 300 second timeout run this:
|
||||
|
||||
```bash
|
||||
cd build/release/stage1
|
||||
ctest -j 4 --output-on-failure --timeout 300
|
||||
```
|
||||
Useful `ctest` flags are `-R <name of test>` to run a single test, and
|
||||
`--rerun-failed` to run all tests that failed during the last run.
|
||||
You can also pass `ctest` flags via `make test ARGS="--rerun-failed"`.
|
||||
|
||||
To get verbose output from ctest pass the `--verbose` command line
|
||||
option. Test output is normally suppressed and only summary
|
||||
information is displayed. This option will show all test output.
|
||||
|
||||
## Test Suite Organization
|
||||
|
||||
All these tests are included by [src/shell/CMakeLists.txt](https://github.com/leanprover/lean4/blob/master/src/shell/CMakeLists.txt):
|
||||
|
||||
- [`tests/lean`](https://github.com/leanprover/lean4/tree/master/tests/lean/): contains tests that come equipped with a
|
||||
.lean.expected.out file. The driver script [`test_single.sh`](https://github.com/leanprover/lean4/tree/master/tests/lean/test_single.sh) runs
|
||||
each test and checks the actual output (*.produced.out) with the
|
||||
checked in expected output.
|
||||
|
||||
- [`tests/lean/run`](https://github.com/leanprover/lean4/tree/master/tests/lean/run/): contains tests that are run through the lean
|
||||
command line one file at a time. These tests only look for error
|
||||
codes and do not check the expected output even though output is
|
||||
produced, it is ignored.
|
||||
|
||||
- [`tests/lean/interactive`](https://github.com/leanprover/lean4/tree/master/tests/lean/interactive/): are designed to test server requests at a
|
||||
given position in the input file. Each .lean file contains comments
|
||||
that indicate how to simulate a client request at that position.
|
||||
using a `--^` point to the line position. Example:
|
||||
```lean,ignore
|
||||
open Foo in
|
||||
theorem tst2 (h : a ≤ b) : a + 2 ≤ b + 2 :=
|
||||
Bla.
|
||||
--^ textDocument/completion
|
||||
```
|
||||
In this example, the test driver [`test_single.sh`](https://github.com/leanprover/lean4/tree/master/tests/lean/interactive/test_single.sh) will simulate an
|
||||
auto-completion request at `Bla.`. The expected output is stored in
|
||||
a .lean.expected.out in the json format that is part of the
|
||||
[Language Server
|
||||
Protocol](https://microsoft.github.io/language-server-protocol/).
|
||||
|
||||
This can also be used to test the following additional requests:
|
||||
```
|
||||
--^ textDocument/hover
|
||||
--^ textDocument/typeDefinition
|
||||
--^ textDocument/definition
|
||||
--^ $/lean/plainGoal
|
||||
--^ $/lean/plainTermGoal
|
||||
--^ insert: ...
|
||||
--^ collectDiagnostics
|
||||
```
|
||||
|
||||
- [`tests/lean/server`](https://github.com/leanprover/lean4/tree/master/tests/lean/server/): Tests more of the Lean `--server` protocol.
|
||||
There are just a few of them, and it uses .log files containing
|
||||
JSON.
|
||||
|
||||
- [`tests/compiler`](https://github.com/leanprover/lean4/tree/master/tests/compiler/): contains tests that will run the Lean compiler and
|
||||
build an executable that is executed and the output is compared to
|
||||
the .lean.expected.out file. This test also contains a subfolder
|
||||
[`foreign`](https://github.com/leanprover/lean4/tree/master/tests/compiler/foreign/) which shows how to extend Lean using C++.
|
||||
|
||||
- [`tests/lean/trust0`](https://github.com/leanprover/lean4/tree/master/tests/lean/trust0): tests that run Lean in a mode that Lean doesn't
|
||||
even trust the .olean files (i.e., trust 0).
|
||||
|
||||
- [`tests/bench`](https://github.com/leanprover/lean4/tree/master/tests/bench/): contains performance tests.
|
||||
|
||||
- [`tests/plugin`](https://github.com/leanprover/lean4/tree/master/tests/plugin/): tests that compiled Lean code can be loaded into
|
||||
`lean` via the `--plugin` command line option.
|
||||
|
||||
## Writing Good Tests
|
||||
|
||||
Every test file should contain:
|
||||
* an initial `/-! -/` module docstring summarizing the test's purpose
|
||||
* a module docstring for each test section that describes what is tested
|
||||
and, if not 100% clear, why that is the desirable behavior
|
||||
|
||||
At the time of writing, most tests do not follow these new guidelines yet.
|
||||
For an example of a conforming test, see [`tests/lean/1971.lean`](https://github.com/leanprover/lean4/tree/master/tests/lean/1971.lean).
|
||||
|
||||
## Fixing Tests
|
||||
|
||||
When the Lean source code or the standard library are modified, some of the
|
||||
tests break because the produced output is slightly different, and we have
|
||||
to reflect the changes in the `.lean.expected.out` files.
|
||||
We should not blindly copy the new produced output since we may accidentally
|
||||
miss a bug introduced by recent changes.
|
||||
The test suite contains commands that allow us to see what changed in a convenient way.
|
||||
First, we must install [meld](http://meldmerge.org/). On Ubuntu, we can do it by simply executing
|
||||
|
||||
```
|
||||
sudo apt-get install meld
|
||||
```
|
||||
|
||||
Now, suppose `bad_class.lean` test is broken. We can see the problem by going to [`tests/lean`](https://github.com/leanprover/lean4/tree/master/tests/lean) directory and
|
||||
executing
|
||||
|
||||
```
|
||||
./test_single.sh -i bad_class.lean
|
||||
```
|
||||
|
||||
When the `-i` option is provided, `meld` is automatically invoked
|
||||
whenever there is discrepancy between the produced and expected
|
||||
outputs. `meld` can also be used to repair the problems.
|
||||
|
||||
In Emacs, we can also execute `M-x lean4-diff-test-file` to check/diff the file of the current buffer.
|
||||
To mass-copy all `.produced.out` files to the respective `.expected.out` file, use `tests/lean/copy-produced`.
|
||||
417
doc/do.md
Normal file
417
doc/do.md
Normal file
@@ -0,0 +1,417 @@
|
||||
# The `do` notation
|
||||
|
||||
Lean is a pure functional programming language, but you can write effectful code using the `do` embedded domain specific language (DSL). The following simple program prints two strings "hello" and "world" in the standard output and terminates with exit code 0. Note that the type of the program is `IO UInt32`. You can read this type as the type of values that perform input-output effects and produce a value of type `UInt32`.
|
||||
|
||||
```lean
|
||||
def main : IO UInt32 := do
|
||||
IO.println "hello"
|
||||
IO.println "world"
|
||||
return 0
|
||||
```
|
||||
The type of `IO.println` is `String → IO Unit`. That is, it is a function from `String` to `IO Unit` which indicates it may perform input-output effects and produce a value of type `Unit`. We often say that functions that may perform effects are *methods*.
|
||||
We also say a method application, such as `IO.println "hello"` is an *action*.
|
||||
Note that the examples above also demonstrates that braceless `do` blocks are whitespace sensitive.
|
||||
If you like `;`s and curly braces, you can write the example above as
|
||||
```lean
|
||||
def main : IO UInt32 := do {
|
||||
IO.println "hello";
|
||||
IO.println "world";
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
Semicolons can be used even when curly braces are not used. They are particularly useful when you want to "pack" more than one action in a single line.
|
||||
```lean
|
||||
def main : IO UInt32 := do
|
||||
IO.println "hello"; IO.println "world"
|
||||
return 0
|
||||
```
|
||||
Whitespace sensitivity in programming languages is a controversial topic
|
||||
among programmers. You should use your own style. We, the Lean developers, **love** the
|
||||
braceless and semicolon-free style.
|
||||
We believe it is clean and beautiful.
|
||||
|
||||
The `do` DSL expands into the core Lean language. Let's inspect the different components using the commands `#print` and `#check`.
|
||||
|
||||
```lean
|
||||
# def main : IO UInt32 := do
|
||||
# IO.println "hello"
|
||||
# IO.println "world"
|
||||
# return 0
|
||||
|
||||
#check IO.println "hello"
|
||||
-- IO Unit
|
||||
#print main
|
||||
-- Output contains the infix operator `>>=` and `pure`
|
||||
-- The following `set_option` disables notation such as `>>=` in the output
|
||||
set_option pp.notation false in
|
||||
#print main
|
||||
-- Output contains `bind` and `pure`
|
||||
#print bind
|
||||
-- bind : {m : Type u → Type v} → [self : Bind m] → {α β : Type u} →
|
||||
-- m α → (α → m β) → m β
|
||||
#print pure
|
||||
-- pure : {m : Type u → Type v} → [self : Pure m] → {α : Type u} →
|
||||
-- α → m α
|
||||
|
||||
-- IO implements the type classes `Bind` and `Pure`.
|
||||
#check (inferInstance : Bind IO)
|
||||
#check (inferInstance : Pure IO)
|
||||
```
|
||||
The types of `bind` and `pure` may look daunting at first sight.
|
||||
They both have many implicit arguments. Let's focus first on the explicit arguments.
|
||||
`bind` has two explicit arguments `m α` and `α → m β`. The first one should
|
||||
be viewed as an action with effects `m` and producing a value of type `α`.
|
||||
The second is a function that takes a value of type `α` and produces an action
|
||||
with effects `m` and a value of type `β`. The result is `m β`. The method `bind` is composing
|
||||
these two actions. We often say `bind` is an abstract semicolon. The method `pure` converts
|
||||
a value `α` into an action that produces an action `m α`.
|
||||
|
||||
Here is the same function being defined using `bind` and `pure` without the `do` DSL.
|
||||
```lean
|
||||
def main : IO UInt32 :=
|
||||
bind (IO.println "hello") fun _ =>
|
||||
bind (IO.println "world") fun _ =>
|
||||
pure 0
|
||||
```
|
||||
|
||||
The notations `let x <- action1; action2` and `let x ← action1; action2` are just syntax sugar for `bind action1 fun x => action2`.
|
||||
Here is a small example using it.
|
||||
```lean
|
||||
def isGreaterThan0 (x : Nat) : IO Bool := do
|
||||
IO.println s!"value: {x}"
|
||||
return x > 0
|
||||
|
||||
def f (x : Nat) : IO Unit := do
|
||||
let c <- isGreaterThan0 x
|
||||
if c then
|
||||
IO.println s!"{x} is greater than 0"
|
||||
else
|
||||
pure ()
|
||||
|
||||
#eval f 10
|
||||
-- value: 10
|
||||
-- 10 is greater than 0
|
||||
```
|
||||
|
||||
|
||||
## Nested actions
|
||||
|
||||
Note that we cannot write `if isGreaterThan0 x then ... else ...` because the condition in a `if-then-else` is a **pure** value without effects, but `isGreaterThan0 x` has type `IO Bool`. You can use the nested action notation to avoid this annoyance. Here is an equivalent definition for `f` using a nested action.
|
||||
```lean
|
||||
# def isGreaterThan0 (x : Nat) : IO Bool := do
|
||||
# IO.println s!"x: {x}"
|
||||
# return x > 0
|
||||
|
||||
def f (x : Nat) : IO Unit := do
|
||||
if (<- isGreaterThan0 x) then
|
||||
IO.println s!"{x} is greater than 0"
|
||||
else
|
||||
pure ()
|
||||
|
||||
#print f
|
||||
```
|
||||
Lean "lifts" the nested actions and introduces the `bind` for us.
|
||||
Here is an example with two nested actions. Note that both actions are executed
|
||||
even if `x = 0`.
|
||||
```lean
|
||||
# def isGreaterThan0 (x : Nat) : IO Bool := do
|
||||
# IO.println s!"x: {x}"
|
||||
# return x > 0
|
||||
|
||||
def f (x y : Nat) : IO Unit := do
|
||||
if (<- isGreaterThan0 x) && (<- isGreaterThan0 y) then
|
||||
IO.println s!"{x} and {y} are greater than 0"
|
||||
else
|
||||
pure ()
|
||||
|
||||
#eval f 0 10
|
||||
-- value: 0
|
||||
-- value: 10
|
||||
|
||||
-- The function `f` above is equivalent to
|
||||
def g (x y : Nat) : IO Unit := do
|
||||
let c1 <- isGreaterThan0 x
|
||||
let c2 <- isGreaterThan0 y
|
||||
if c1 && c2 then
|
||||
IO.println s!"{x} and {y} are greater than 0"
|
||||
else
|
||||
pure ()
|
||||
|
||||
theorem fgEqual : f = g :=
|
||||
rfl -- proof by reflexivity
|
||||
```
|
||||
Here are two ways to achieve the short-circuit semantics in the example above
|
||||
```lean
|
||||
# def isGreaterThan0 (x : Nat) : IO Bool := do
|
||||
# IO.println s!"x: {x}"
|
||||
# return x > 0
|
||||
|
||||
def f1 (x y : Nat) : IO Unit := do
|
||||
if (<- isGreaterThan0 x <&&> isGreaterThan0 y) then
|
||||
IO.println s!"{x} and {y} are greater than 0"
|
||||
else
|
||||
pure ()
|
||||
|
||||
-- `<&&>` is the effectful version of `&&`
|
||||
-- Given `x y : IO Bool`, `x <&&> y` : m Bool`
|
||||
-- It only executes `y` if `x` returns `true`.
|
||||
|
||||
#eval f1 0 10
|
||||
-- value: 0
|
||||
#eval f1 1 10
|
||||
-- value: 1
|
||||
-- value: 10
|
||||
-- 1 and 10 are greater than 0
|
||||
|
||||
def f2 (x y : Nat) : IO Unit := do
|
||||
if (<- isGreaterThan0 x) then
|
||||
if (<- isGreaterThan0 y) then
|
||||
IO.println s!"{x} and {y} are greater than 0"
|
||||
else
|
||||
pure ()
|
||||
else
|
||||
pure ()
|
||||
```
|
||||
|
||||
## `if-then` notation
|
||||
|
||||
In the `do` DSL, we can write `if c then action` as a shorthand for `if c then action else pure ()`. Here is the method `f2` using this shorthand.
|
||||
```lean
|
||||
# def isGreaterThan0 (x : Nat) : IO Bool := do
|
||||
# IO.println s!"x: {x}"
|
||||
# return x > 0
|
||||
|
||||
def f2 (x y : Nat) : IO Unit := do
|
||||
if (<- isGreaterThan0 x) then
|
||||
if (<- isGreaterThan0 y) then
|
||||
IO.println s!"{x} and {y} are greater than 0"
|
||||
```
|
||||
|
||||
## Reassignments
|
||||
|
||||
When writing effectful code, it is natural to think imperatively.
|
||||
For example, suppose we want to create an empty array `xs`,
|
||||
add `0` if some condition holds, add `1` if another condition holds,
|
||||
and then print it. In the following example, we use variable
|
||||
"shadowing" to simulate this kind of "update".
|
||||
|
||||
```lean
|
||||
def f (b1 b2 : Bool) : IO Unit := do
|
||||
let xs := #[]
|
||||
let xs := if b1 then xs.push 0 else xs
|
||||
let xs := if b2 then xs.push 1 else xs
|
||||
IO.println xs
|
||||
|
||||
#eval f true true
|
||||
-- #[0, 1]
|
||||
#eval f false true
|
||||
-- #[1]
|
||||
#eval f true false
|
||||
-- #[0]
|
||||
#eval f false false
|
||||
-- #[]
|
||||
```
|
||||
|
||||
We can use tuples to simulate updates on multiple variables.
|
||||
|
||||
```lean
|
||||
def f (b1 b2 : Bool) : IO Unit := do
|
||||
let xs := #[]
|
||||
let ys := #[]
|
||||
let (xs, ys) := if b1 then (xs.push 0, ys) else (xs, ys.push 0)
|
||||
let (xs, ys) := if b2 then (xs.push 1, ys) else (xs, ys.push 1)
|
||||
IO.println s!"xs: {xs}, ys: {ys}"
|
||||
|
||||
#eval f true false
|
||||
-- xs: #[0], ys: #[1]
|
||||
```
|
||||
|
||||
We can also simulate the control-flow above using *join-points*.
|
||||
A join-point is a `let` that is always tail called and fully applied.
|
||||
The Lean compiler implements them using `goto`s.
|
||||
Here is the same example using join-points.
|
||||
|
||||
```lean
|
||||
def f (b1 b2 : Bool) : IO Unit := do
|
||||
let jp1 xs ys := IO.println s!"xs: {xs}, ys: {ys}"
|
||||
let jp2 xs ys := if b2 then jp1 (xs.push 1) ys else jp1 xs (ys.push 1)
|
||||
let xs := #[]
|
||||
let ys := #[]
|
||||
if b1 then jp2 (xs.push 0) ys else jp2 xs (ys.push 0)
|
||||
|
||||
#eval f true false
|
||||
-- xs: #[0], ys: #[1]
|
||||
```
|
||||
|
||||
You can capture complex control-flow using join-points.
|
||||
The `do` DSL offers the variable reassignment feature to make this kind of code more comfortable to write. In the following example, the `mut` modifier at `let mut xs := #[]` indicates that variable `xs` can be reassigned. The example contains two reassignments `xs := xs.push 0` and `xs := xs.push 1`. The reassignments are compiled using join-points. There is no hidden state being updated.
|
||||
|
||||
```lean
|
||||
def f (b1 b2 : Bool) : IO Unit := do
|
||||
let mut xs := #[]
|
||||
if b1 then xs := xs.push 0
|
||||
if b2 then xs := xs.push 1
|
||||
IO.println xs
|
||||
|
||||
#eval f true true
|
||||
-- #[0, 1]
|
||||
```
|
||||
The notation `x <- action` reassigns `x` with the value produced by the action. It is equivalent to `x := (<- action)`
|
||||
|
||||
## Iteration
|
||||
|
||||
The `do` DSL provides a unified notation for iterating over datastructures. Here are a few examples.
|
||||
|
||||
```lean
|
||||
def sum (xs : Array Nat) : IO Nat := do
|
||||
let mut s := 0
|
||||
for x in xs do
|
||||
IO.println s!"x: {x}"
|
||||
s := s + x
|
||||
return s
|
||||
|
||||
#eval sum #[1, 2, 3]
|
||||
-- x: 1
|
||||
-- x: 2
|
||||
-- x: 3
|
||||
-- 6
|
||||
|
||||
-- We can write pure code using the `Id.run <| do` DSL too.
|
||||
def sum' (xs : Array Nat) : Nat := Id.run <| do
|
||||
let mut s := 0
|
||||
for x in xs do
|
||||
s := s + x
|
||||
return s
|
||||
|
||||
#eval sum' #[1, 2, 3]
|
||||
-- 6
|
||||
|
||||
def sumEven (xs : Array Nat) : IO Nat := do
|
||||
let mut s := 0
|
||||
for x in xs do
|
||||
if x % 2 == 0 then
|
||||
IO.println s!"x: {x}"
|
||||
s := s + x
|
||||
return s
|
||||
|
||||
#eval sumEven #[1, 2, 3, 6]
|
||||
-- x: 2
|
||||
-- x: 6
|
||||
-- 8
|
||||
|
||||
def splitEvenOdd (xs : List Nat) : IO Unit := do
|
||||
let mut evens := #[]
|
||||
let mut odds := #[]
|
||||
for x in xs do
|
||||
if x % 2 == 0 then
|
||||
evens := evens.push x
|
||||
else
|
||||
odds := odds.push x
|
||||
IO.println s!"evens: {evens}, odds: {odds}"
|
||||
|
||||
#eval splitEvenOdd [1, 2, 3, 4]
|
||||
-- evens: #[2, 4], odds: #[1, 3]
|
||||
|
||||
def findNatLessThan (x : Nat) (p : Nat → Bool) : IO Nat := do
|
||||
-- [:x] is notation for the range [0, x)
|
||||
for i in [:x] do
|
||||
if p i then
|
||||
return i -- `return` from the `do` block
|
||||
throw (IO.userError "value not found")
|
||||
|
||||
#eval findNatLessThan 10 (fun x => x > 5 && x % 4 == 0)
|
||||
-- 8
|
||||
|
||||
def sumOddUpTo (xs : List Nat) (threshold : Nat) : IO Nat := do
|
||||
let mut s := 0
|
||||
for x in xs do
|
||||
if x % 2 == 0 then
|
||||
continue -- it behaves like the `continue` statement in imperative languages
|
||||
IO.println s!"x: {x}"
|
||||
s := s + x
|
||||
if s > threshold then
|
||||
break -- it behaves like the `break` statement in imperative languages
|
||||
IO.println s!"result: {s}"
|
||||
return s
|
||||
|
||||
#eval sumOddUpTo [2, 3, 4, 11, 20, 31, 41, 51, 107] 40
|
||||
-- x: 3
|
||||
-- x: 11
|
||||
-- x: 31
|
||||
-- result: 45
|
||||
-- 45
|
||||
```
|
||||
|
||||
TODO: describe `forIn`
|
||||
|
||||
## Try-catch
|
||||
|
||||
TODO
|
||||
|
||||
## Returning early from a failed match
|
||||
|
||||
Inside a `do` block, the pattern `let _ ← <success> | <fail>` will continue with the rest of the block if the match on the left hand side succeeds, but will execute the right hand side and exit the block on failure:
|
||||
|
||||
```lean
|
||||
def showUserInfo (getUsername getFavoriteColor : IO (Option String)) : IO Unit := do
|
||||
let some n ← getUsername | IO.println "no username!"
|
||||
IO.println s!"username: {n}"
|
||||
let some c ← getFavoriteColor | IO.println "user didn't provide a favorite color!"
|
||||
IO.println s!"favorite color: {c}"
|
||||
|
||||
-- username: JohnDoe
|
||||
-- favorite color: red
|
||||
#eval showUserInfo (pure <| some "JohnDoe") (pure <| some "red")
|
||||
|
||||
-- no username
|
||||
#eval showUserInfo (pure none) (pure <| some "purple")
|
||||
|
||||
-- username: JaneDoe
|
||||
-- user didn't provide a favorite color
|
||||
#eval showUserInfo (pure <| some "JaneDoe") (pure none)
|
||||
```
|
||||
|
||||
## If-let
|
||||
|
||||
Inside a `do` block, users can employ the `if let` pattern to destructure actions:
|
||||
|
||||
```lean
|
||||
def tryIncrement (getInput : IO (Option Nat)) : IO (Except String Nat) := do
|
||||
if let some n ← getInput
|
||||
then return Except.ok n.succ
|
||||
else return Except.error "argument was `none`"
|
||||
|
||||
-- Except.ok 2
|
||||
#eval tryIncrement (pure <| some 1)
|
||||
|
||||
-- Except.error "argument was `none`"
|
||||
#eval tryIncrement (pure <| none)
|
||||
```
|
||||
|
||||
## Pattern matching
|
||||
|
||||
TODO
|
||||
|
||||
## Monads
|
||||
|
||||
TODO
|
||||
|
||||
## ReaderT
|
||||
|
||||
TODO
|
||||
|
||||
## StateT
|
||||
|
||||
TODO
|
||||
|
||||
## StateRefT
|
||||
|
||||
TODO
|
||||
|
||||
## ExceptT
|
||||
|
||||
TODO
|
||||
|
||||
## MonadLift and automatic lifting
|
||||
|
||||
TODO
|
||||
8
doc/elaborators.md
Normal file
8
doc/elaborators.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## Elaborators
|
||||
|
||||
TODO. See [Lean Together 2021: Metaprogramming in Lean
|
||||
4](https://youtu.be/hxQ1vvhYN_U) for an overview as well [the
|
||||
continuation](https://youtu.be/vy4JWIiiXSY) about tactic programming.
|
||||
For more information on antiquotations, see also §4.1 of [Beyond
|
||||
Notations: Hygienic Macro Expansion for Theorem Proving
|
||||
Languages](https://arxiv.org/pdf/2001.10490.pdf#page=11).
|
||||
190
doc/enum.md
Normal file
190
doc/enum.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# Enumerated Types
|
||||
|
||||
The simplest kind of inductive type is simply a type with a finite, enumerated list of elements.
|
||||
The following command declares the enumerated type `Weekday`.
|
||||
```lean
|
||||
inductive Weekday where
|
||||
| sunday : Weekday
|
||||
| monday : Weekday
|
||||
| tuesday : Weekday
|
||||
| wednesday : Weekday
|
||||
| thursday : Weekday
|
||||
| friday : Weekday
|
||||
| saturday : Weekday
|
||||
```
|
||||
|
||||
The `Weekday` type has 7 constructors/elements. The constructors live in the `Weekday` namespace
|
||||
Think of `sunday`, `monday`, …, `saturday` as being distinct elements of `Weekday`,
|
||||
with no other distinguishing properties.
|
||||
```lean
|
||||
# inductive Weekday where
|
||||
# | sunday : Weekday
|
||||
# | monday : Weekday
|
||||
# | tuesday : Weekday
|
||||
# | wednesday : Weekday
|
||||
# | thursday : Weekday
|
||||
# | friday : Weekday
|
||||
# | saturday : Weekday
|
||||
#check Weekday.sunday -- Weekday
|
||||
#check Weekday.monday -- Weekday
|
||||
```
|
||||
|
||||
You can define functions by pattern matching.
|
||||
The following function converts a `Weekday` into a natural number.
|
||||
```lean
|
||||
# inductive Weekday where
|
||||
# | sunday : Weekday
|
||||
# | monday : Weekday
|
||||
# | tuesday : Weekday
|
||||
# | wednesday : Weekday
|
||||
# | thursday : Weekday
|
||||
# | friday : Weekday
|
||||
# | saturday : Weekday
|
||||
def natOfWeekday (d : Weekday) : Nat :=
|
||||
match d with
|
||||
| Weekday.sunday => 1
|
||||
| Weekday.monday => 2
|
||||
| Weekday.tuesday => 3
|
||||
| Weekday.wednesday => 4
|
||||
| Weekday.thursday => 5
|
||||
| Weekday.friday => 6
|
||||
| Weekday.saturday => 7
|
||||
|
||||
#eval natOfWeekday Weekday.tuesday -- 3
|
||||
```
|
||||
|
||||
It is often useful to group definitions related to a type in a namespace with the same name.
|
||||
For example, we can put the function above into the ``Weekday`` namespace.
|
||||
We are then allowed to use the shorter name when we open the namespace.
|
||||
|
||||
In the following example, we define functions from ``Weekday`` to ``Weekday`` in the namespace `Weekday`.
|
||||
```lean
|
||||
# inductive Weekday where
|
||||
# | sunday : Weekday
|
||||
# | monday : Weekday
|
||||
# | tuesday : Weekday
|
||||
# | wednesday : Weekday
|
||||
# | thursday : Weekday
|
||||
# | friday : Weekday
|
||||
# | saturday : Weekday
|
||||
namespace Weekday
|
||||
|
||||
def next (d : Weekday) : Weekday :=
|
||||
match d with
|
||||
| sunday => monday
|
||||
| monday => tuesday
|
||||
| tuesday => wednesday
|
||||
| wednesday => thursday
|
||||
| thursday => friday
|
||||
| friday => saturday
|
||||
| saturday => sunday
|
||||
|
||||
end Weekday
|
||||
```
|
||||
It is so common to start a definition with a `match` in Lean, that Lean provides a syntax sugar for it.
|
||||
```lean
|
||||
# inductive Weekday where
|
||||
# | sunday : Weekday
|
||||
# | monday : Weekday
|
||||
# | tuesday : Weekday
|
||||
# | wednesday : Weekday
|
||||
# | thursday : Weekday
|
||||
# | friday : Weekday
|
||||
# | saturday : Weekday
|
||||
# namespace Weekday
|
||||
def previous : Weekday -> Weekday
|
||||
| sunday => saturday
|
||||
| monday => sunday
|
||||
| tuesday => monday
|
||||
| wednesday => tuesday
|
||||
| thursday => wednesday
|
||||
| friday => thursday
|
||||
| saturday => friday
|
||||
# end Weekday
|
||||
```
|
||||
We can use the command `#eval` to test our definitions.
|
||||
```lean
|
||||
# inductive Weekday where
|
||||
# | sunday : Weekday
|
||||
# | monday : Weekday
|
||||
# | tuesday : Weekday
|
||||
# | wednesday : Weekday
|
||||
# | thursday : Weekday
|
||||
# | friday : Weekday
|
||||
# | saturday : Weekday
|
||||
# namespace Weekday
|
||||
# def next (d : Weekday) : Weekday :=
|
||||
# match d with
|
||||
# | sunday => monday
|
||||
# | monday => tuesday
|
||||
# | tuesday => wednesday
|
||||
# | wednesday => thursday
|
||||
# | thursday => friday
|
||||
# | friday => saturday
|
||||
# | saturday => sunday
|
||||
# def previous : Weekday -> Weekday
|
||||
# | sunday => saturday
|
||||
# | monday => sunday
|
||||
# | tuesday => monday
|
||||
# | wednesday => tuesday
|
||||
# | thursday => wednesday
|
||||
# | friday => thursday
|
||||
# | saturday => friday
|
||||
def toString : Weekday -> String
|
||||
| sunday => "Sunday"
|
||||
| monday => "Monday"
|
||||
| tuesday => "Tuesday"
|
||||
| wednesday => "Wednesday"
|
||||
| thursday => "Thursday"
|
||||
| friday => "Friday"
|
||||
| saturday => "Saturday"
|
||||
|
||||
#eval toString (next sunday) -- "Monday"
|
||||
#eval toString (next tuesday) -- "Wednesday"
|
||||
#eval toString (previous wednesday) -- "Tuesday"
|
||||
#eval toString (next (previous sunday)) -- "Sunday"
|
||||
#eval toString (next (previous monday)) -- "Monday"
|
||||
-- ..
|
||||
# end Weekday
|
||||
```
|
||||
We can now prove the general theorem that ``next (previous d) = d`` for any weekday ``d``.
|
||||
The idea is to perform a proof by cases using `match`, and rely on the fact for each constructor both
|
||||
sides of the equality reduce to the same term.
|
||||
```lean
|
||||
# inductive Weekday where
|
||||
# | sunday : Weekday
|
||||
# | monday : Weekday
|
||||
# | tuesday : Weekday
|
||||
# | wednesday : Weekday
|
||||
# | thursday : Weekday
|
||||
# | friday : Weekday
|
||||
# | saturday : Weekday
|
||||
# namespace Weekday
|
||||
# def next (d : Weekday) : Weekday :=
|
||||
# match d with
|
||||
# | sunday => monday
|
||||
# | monday => tuesday
|
||||
# | tuesday => wednesday
|
||||
# | wednesday => thursday
|
||||
# | thursday => friday
|
||||
# | friday => saturday
|
||||
# | saturday => sunday
|
||||
# def previous : Weekday -> Weekday
|
||||
# | sunday => saturday
|
||||
# | monday => sunday
|
||||
# | tuesday => monday
|
||||
# | wednesday => tuesday
|
||||
# | thursday => wednesday
|
||||
# | friday => thursday
|
||||
# | saturday => friday
|
||||
theorem nextOfPrevious (d : Weekday) : next (previous d) = d :=
|
||||
match d with
|
||||
| sunday => rfl
|
||||
| monday => rfl
|
||||
| tuesday => rfl
|
||||
| wednesday => rfl
|
||||
| thursday => rfl
|
||||
| friday => rfl
|
||||
| saturday => rfl
|
||||
# end Weekday
|
||||
```
|
||||
9
doc/examples.md
Normal file
9
doc/examples.md
Normal file
@@ -0,0 +1,9 @@
|
||||
Examples
|
||||
========
|
||||
|
||||
- [Palindromes](examples/palindromes.lean.md)
|
||||
- [Binary Search Trees](examples/bintree.lean.md)
|
||||
- [A Certified Type Checker](examples/tc.lean.md)
|
||||
- [The Well-Typed Interpreter](examples/interp.lean.md)
|
||||
- [Dependent de Bruijn Indices](examples/deBruijn.lean.md)
|
||||
- [Parametric Higher-Order Abstract Syntax](examples/phoas.lean.md)
|
||||
2
doc/examples/.gitignore
vendored
2
doc/examples/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
*.out.produced
|
||||
*.exit.produced
|
||||
@@ -4,16 +4,16 @@ def ack : Nat → Nat → Nat
|
||||
| 0, y => y+1
|
||||
| x+1, 0 => ack x 1
|
||||
| x+1, y+1 => ack x (ack (x+1) y)
|
||||
termination_by x y => (x, y)
|
||||
termination_by ack x y => (x, y)
|
||||
|
||||
def sum (a : Array Int) : Int :=
|
||||
let rec go (i : Nat) :=
|
||||
if _ : i < a.size then
|
||||
if i < a.size then
|
||||
a[i] + go (i+1)
|
||||
else
|
||||
0
|
||||
termination_by a.size - i
|
||||
go 0
|
||||
termination_by go i => a.size - i
|
||||
|
||||
set_option pp.proofs true
|
||||
#print sum.go
|
||||
|
||||
@@ -4,42 +4,43 @@ open Lean Meta
|
||||
|
||||
def ctor (mvarId : MVarId) (idx : Nat) : MetaM (List MVarId) := do
|
||||
/- Set `MetaM` context using `mvarId` -/
|
||||
mvarId.withContext do
|
||||
withMVarContext mvarId do
|
||||
/- Fail if the metavariable is already assigned. -/
|
||||
mvarId.checkNotAssigned `ctor
|
||||
checkNotAssigned mvarId `ctor
|
||||
/- Retrieve the target type, instantiateMVars, and use `whnf`. -/
|
||||
let target ← mvarId.getType'
|
||||
let target ← getMVarType' mvarId
|
||||
let .const declName us := target.getAppFn
|
||||
| throwTacticEx `ctor mvarId "target is not an inductive datatype"
|
||||
let .inductInfo { ctors, .. } ← getConstInfo declName
|
||||
| throwTacticEx `ctor mvarId "target is not an inductive datatype"
|
||||
if idx = 0 then
|
||||
throwTacticEx `ctor mvarId "invalid index, it must be > 0"
|
||||
throwTacticEx `ctor mvarId "invalid index, it must be > 0"
|
||||
else if h : idx - 1 < ctors.length then
|
||||
mvarId.apply (.const ctors[idx - 1] us)
|
||||
apply mvarId (.const ctors[idx - 1] us)
|
||||
else
|
||||
throwTacticEx `ctor mvarId "invalid index, inductive datatype has only {ctors.length} constructors"
|
||||
throwTacticEx `ctor mvarId "invalid index, inductive datatype has only {ctors.length} contructors"
|
||||
|
||||
open Elab Tactic
|
||||
|
||||
elab "ctor" idx:num : tactic =>
|
||||
elab "ctor" idx:num : tactic =>
|
||||
liftMetaTactic (ctor · idx.getNat)
|
||||
|
||||
example (p : Prop) : p := by
|
||||
example (p : Prop) : p := by
|
||||
ctor 1 -- Error
|
||||
|
||||
example (h : q) : p ∨ q := by
|
||||
example (h : q) : p ∨ q := by
|
||||
ctor 0 -- Error
|
||||
exact h
|
||||
|
||||
example (h : q) : p ∨ q := by
|
||||
example (h : q) : p ∨ q := by
|
||||
ctor 3 -- Error
|
||||
exact h
|
||||
|
||||
example (h : q) : p ∨ q := by
|
||||
example (h : q) : p ∨ q := by
|
||||
ctor 2
|
||||
exact h
|
||||
|
||||
example (h : q) : p ∨ q := by
|
||||
example (h : q) : p ∨ q := by
|
||||
ctor 1
|
||||
exact h -- Error
|
||||
exact h -- Error
|
||||
|
||||
|
||||
@@ -5,15 +5,15 @@ open Lean Meta
|
||||
def ex1 (declName : Name) : MetaM Unit := do
|
||||
let info ← getConstInfo declName
|
||||
IO.println s!"{declName} : {← ppExpr info.type}"
|
||||
if let some val := info.value? then
|
||||
if let some val := info.value? then
|
||||
IO.println s!"{declName} : {← ppExpr val}"
|
||||
|
||||
|
||||
#eval ex1 ``Nat
|
||||
|
||||
def ex2 (declName : Name) : MetaM Unit := do
|
||||
let info ← getConstInfo declName
|
||||
trace[Meta.debug] "{declName} : {info.type}"
|
||||
if let some val := info.value? then
|
||||
if let some val := info.value? then
|
||||
trace[Meta.debug] "{declName} : {val}"
|
||||
|
||||
#eval ex2 ``Add.add
|
||||
@@ -29,10 +29,10 @@ def ex3 (declName : Name) : MetaM Unit := do
|
||||
for x in xs do
|
||||
trace[Meta.debug] "{x} : {← inferType x}"
|
||||
|
||||
def myMin [LT α] [DecidableLT α] (a b : α) : α :=
|
||||
if a < b then
|
||||
def myMin [LT α] [DecidableRel (α := α) (·<·)] (a b : α) : α :=
|
||||
if a < b then
|
||||
a
|
||||
else
|
||||
else
|
||||
b
|
||||
|
||||
set_option trace.Meta.debug true in
|
||||
@@ -40,7 +40,7 @@ set_option trace.Meta.debug true in
|
||||
|
||||
def ex4 : MetaM Unit := do
|
||||
let nat := mkConst ``Nat
|
||||
withLocalDeclD `a nat fun a =>
|
||||
withLocalDeclD `a nat fun a =>
|
||||
withLocalDeclD `b nat fun b => do
|
||||
let e ← mkAppM ``HAdd.hAdd #[a, b]
|
||||
trace[Meta.debug] "{e} : {← inferType e}"
|
||||
@@ -66,17 +66,15 @@ open Elab Term
|
||||
|
||||
def ex5 : TermElabM Unit := do
|
||||
let nat := Lean.mkConst ``Nat
|
||||
withLocalDeclD `a nat fun a => do
|
||||
withLocalDeclD `a nat fun a => do
|
||||
withLocalDeclD `b nat fun b => do
|
||||
let ab ← mkAppM ``HAdd.hAdd #[a, b]
|
||||
let abStx ← exprToSyntax ab
|
||||
let aStx ← exprToSyntax a
|
||||
let stx ← `(fun x => if x < 10 then $abStx + x else x + $aStx)
|
||||
let stx ← `(fun x => if x < 10 then $(← exprToSyntax ab) + x else x + $(← exprToSyntax a))
|
||||
let e ← elabTerm stx none
|
||||
trace[Meta.debug] "{e} : {← inferType e}"
|
||||
let e := mkApp e (mkNatLit 5)
|
||||
let e ← whnf e
|
||||
trace[Meta.debug] "{e}"
|
||||
|
||||
|
||||
set_option trace.Meta.debug true in
|
||||
#eval ex5
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# IJCAR 2026: `grind`, An SMT-Inspired Tactic for Lean 4
|
||||
|
||||
Ancillary materials for the paper.
|
||||
|
||||
- `examples.lean`: interactive examples from the paper
|
||||
- `analyze_grind_loc.py`: script used for the evaluation section, analyzing `grind` adoption and lines-of-code changes in Mathlib
|
||||
@@ -1,401 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Analyze grind adoption LoC changes in mathlib.
|
||||
|
||||
For each theorem/lemma in master that uses grind, find the most recent
|
||||
commit where it didn't use grind, and measure the LoC change.
|
||||
|
||||
This script was used in preparing the "Evaluation" section of the grind paper.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import re
|
||||
import csv
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from typing import Iterator
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
@dataclass
|
||||
class GrindUsage:
|
||||
file: str
|
||||
line_no: int
|
||||
decl_name: str
|
||||
decl_type: str # theorem, lemma, def, example, etc.
|
||||
|
||||
|
||||
@dataclass
|
||||
class LocChange:
|
||||
file: str
|
||||
decl_name: str
|
||||
decl_type: str
|
||||
old_loc: int
|
||||
new_loc: int
|
||||
loc_saved: int
|
||||
commit_sha: str
|
||||
commit_date: str
|
||||
|
||||
|
||||
def run_git(args: list[str], repo: str = ".") -> str:
|
||||
"""Run a git command and return stdout."""
|
||||
result = subprocess.run(
|
||||
["git", "-C", repo] + args,
|
||||
capture_output=True, text=True, check=True
|
||||
)
|
||||
return result.stdout
|
||||
|
||||
|
||||
def run_git_safe(args: list[str], repo: str = ".") -> str | None:
|
||||
"""Run a git command, return None on failure."""
|
||||
result = subprocess.run(
|
||||
["git", "-C", repo] + args,
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
return result.stdout
|
||||
|
||||
|
||||
@lru_cache(maxsize=4096)
|
||||
def get_file_at_commit(repo: str, commit: str, file_path: str) -> str | None:
|
||||
"""Get file contents at a specific commit (cached)."""
|
||||
return run_git_safe(["show", f"{commit}:{file_path}"], repo)
|
||||
|
||||
|
||||
def find_grind_usages(repo: str = ".") -> tuple[list[GrindUsage], int, int]:
|
||||
"""Find all declarations using grind in current master.
|
||||
Returns (usages, total_grind_calls, grind_in_decls) where:
|
||||
- total_grind_calls is the count of grind tactic calls (after filtering comments/attrs)
|
||||
- grind_in_decls is the count of those that are inside named declarations
|
||||
"""
|
||||
# Use git grep to find lines containing 'grind' (excludes lake packages)
|
||||
result = run_git(["grep", "-n", "grind", "master", "--", "Mathlib/"], repo)
|
||||
|
||||
usages = []
|
||||
seen = set() # (file, decl_name) to dedupe
|
||||
total_grind_calls = 0
|
||||
grind_in_decls = 0
|
||||
|
||||
for line in result.strip().split('\n'):
|
||||
if not line:
|
||||
continue
|
||||
# Format: master:path/to/file.lean:123:line content
|
||||
match = re.match(r'^master:(.+\.lean):(\d+):(.*)$', line)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
file_path, line_no_str, content = match.groups()
|
||||
line_no = int(line_no_str)
|
||||
|
||||
# Skip comments and attributes (not tactic calls)
|
||||
content_stripped = content.strip()
|
||||
if content_stripped.startswith('--') or content_stripped.startswith('/-'):
|
||||
continue
|
||||
if content_stripped.startswith('attribute'):
|
||||
continue
|
||||
if '@[' in content and 'grind' in content:
|
||||
# Could be an attribute like @[grind =], skip
|
||||
if 'by' not in content and ':=' not in content:
|
||||
continue
|
||||
|
||||
total_grind_calls += 1
|
||||
|
||||
# Find the declaration this grind belongs to
|
||||
decl_name, decl_type = find_decl_at_line(repo, file_path, line_no)
|
||||
if decl_name is None:
|
||||
continue
|
||||
|
||||
grind_in_decls += 1
|
||||
|
||||
key = (file_path, decl_name)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
|
||||
usages.append(GrindUsage(
|
||||
file=file_path,
|
||||
line_no=line_no,
|
||||
decl_name=decl_name,
|
||||
decl_type=decl_type
|
||||
))
|
||||
|
||||
return usages, total_grind_calls, grind_in_decls
|
||||
|
||||
|
||||
def find_decl_at_line(repo: str, file_path: str, grind_line: int) -> tuple[str | None, str | None]:
|
||||
"""
|
||||
Find the declaration name and type that contains the grind at the given line.
|
||||
Search backwards from grind_line to find the most recent declaration.
|
||||
"""
|
||||
# Get file content at master
|
||||
content = get_file_at_commit(repo, "master", file_path)
|
||||
if content is None:
|
||||
return None, None
|
||||
|
||||
lines = content.split('\n')
|
||||
|
||||
# Search backwards from grind_line for a declaration
|
||||
# Match declarations with optional leading modifiers and attributes
|
||||
decl_pattern = re.compile(r'^(?:@\[.*?\]\s*)*(?:private\s+|protected\s+|noncomputable\s+|scoped\s+)*(theorem|lemma|def|example|instance|abbrev|structure|class)\s+(\w+)')
|
||||
|
||||
for i in range(grind_line - 1, -1, -1):
|
||||
if i >= len(lines):
|
||||
continue
|
||||
line = lines[i]
|
||||
match = decl_pattern.match(line)
|
||||
if match:
|
||||
return match.group(2), match.group(1)
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def find_grind_introduction_commit(repo: str, file_path: str, decl_name: str) -> str | None:
|
||||
"""
|
||||
Find the commit that introduced grind to this declaration.
|
||||
Returns None if the declaration was born with grind.
|
||||
"""
|
||||
# First, find the line range of the declaration in master
|
||||
content = get_file_at_commit(repo, "master", file_path)
|
||||
if content is None:
|
||||
return None
|
||||
|
||||
lines = content.split('\n')
|
||||
decl_start = None
|
||||
decl_end = None
|
||||
|
||||
# Find declaration start
|
||||
decl_pattern = re.compile(rf'^(?:@\[.*?\]\s*)*(?:private\s+|protected\s+|noncomputable\s+|scoped\s+)*(theorem|lemma|def|example|instance|abbrev|structure|class)\s+{re.escape(decl_name)}\b')
|
||||
for i, line in enumerate(lines):
|
||||
if decl_pattern.match(line):
|
||||
decl_start = i
|
||||
break
|
||||
|
||||
if decl_start is None:
|
||||
return None
|
||||
|
||||
# Find declaration end (next top-level declaration or EOF)
|
||||
end_patterns = re.compile(r'^(?:private\s+|protected\s+|noncomputable\s+|scoped\s+)*(theorem|lemma|def|example|instance|abbrev|structure|class|namespace|section|end\s|@\[|#|/-)')
|
||||
for i in range(decl_start + 1, len(lines)):
|
||||
line = lines[i]
|
||||
if line and not line[0].isspace() and end_patterns.match(line):
|
||||
decl_end = i
|
||||
break
|
||||
if decl_end is None:
|
||||
decl_end = len(lines)
|
||||
|
||||
# Find grind line within declaration
|
||||
grind_line = None
|
||||
for i in range(decl_start, decl_end):
|
||||
if 'grind' in lines[i]:
|
||||
grind_line = i + 1 # 1-indexed
|
||||
break
|
||||
|
||||
if grind_line is None:
|
||||
return None
|
||||
|
||||
# Use git blame to find when that grind line was added
|
||||
blame_result = run_git_safe(["blame", "-L", f"{grind_line},{grind_line}", "--porcelain", "master", "--", file_path], repo)
|
||||
if blame_result is None:
|
||||
return None
|
||||
|
||||
# First line of porcelain output is the commit SHA
|
||||
first_line = blame_result.split('\n')[0]
|
||||
commit_sha = first_line.split()[0]
|
||||
|
||||
# Check if this declaration existed before this commit (without grind)
|
||||
parent_sha = run_git_safe(["rev-parse", f"{commit_sha}^"], repo)
|
||||
if parent_sha is None:
|
||||
return None # Initial commit, born with grind
|
||||
parent_sha = parent_sha.strip()
|
||||
|
||||
# Check if declaration existed in parent
|
||||
parent_content = get_file_at_commit(repo, parent_sha, file_path)
|
||||
if parent_content is None:
|
||||
# File didn't exist in parent - might be new file or renamed
|
||||
return None
|
||||
|
||||
# Check if declaration existed and didn't have grind
|
||||
if decl_name not in parent_content:
|
||||
return None # Declaration didn't exist - born with grind
|
||||
|
||||
# Check if it already had grind in parent
|
||||
parent_lines = parent_content.split('\n')
|
||||
in_decl = False
|
||||
for line in parent_lines:
|
||||
if decl_pattern.match(line):
|
||||
in_decl = True
|
||||
elif in_decl:
|
||||
if line and not line[0].isspace() and end_patterns.match(line):
|
||||
break
|
||||
if 'grind' in line:
|
||||
# Already had grind in parent — not the introduction commit
|
||||
return None
|
||||
|
||||
return commit_sha
|
||||
|
||||
|
||||
def extract_proof_loc(repo: str, file_path: str, decl_name: str, commit: str) -> int | None:
|
||||
"""
|
||||
Extract the number of lines in a declaration's proof at a given commit.
|
||||
Returns None if the declaration doesn't exist at that commit.
|
||||
"""
|
||||
content = get_file_at_commit(repo, commit, file_path)
|
||||
if content is None:
|
||||
return None
|
||||
|
||||
lines = content.split('\n')
|
||||
|
||||
# Find declaration start
|
||||
decl_pattern = re.compile(rf'^(?:@\[.*?\]\s*)*(?:private\s+|protected\s+|noncomputable\s+|scoped\s+)*(theorem|lemma|def|example|instance|abbrev|structure|class)\s+{re.escape(decl_name)}\b')
|
||||
decl_start = None
|
||||
for i, line in enumerate(lines):
|
||||
if decl_pattern.match(line):
|
||||
decl_start = i
|
||||
break
|
||||
|
||||
if decl_start is None:
|
||||
return None
|
||||
|
||||
# Find declaration end
|
||||
end_patterns = re.compile(r'^(?:private\s+|protected\s+|noncomputable\s+|scoped\s+)*(theorem|lemma|def|example|instance|abbrev|structure|class|namespace|section|end\s|@\[|#|/-)')
|
||||
decl_end = None
|
||||
for i in range(decl_start + 1, len(lines)):
|
||||
line = lines[i]
|
||||
if line and not line[0].isspace() and end_patterns.match(line):
|
||||
decl_end = i
|
||||
break
|
||||
if decl_end is None:
|
||||
decl_end = len(lines)
|
||||
|
||||
# Count non-empty lines in declaration
|
||||
loc = sum(1 for i in range(decl_start, decl_end) if lines[i].strip())
|
||||
return loc
|
||||
|
||||
|
||||
def get_commit_date(repo: str, sha: str) -> str:
|
||||
"""Get the date of a commit."""
|
||||
result = run_git(["log", "-1", "--format=%ci", sha], repo)
|
||||
return result.strip().split()[0] # Just the date part
|
||||
|
||||
|
||||
def analyze_usage_detailed(repo: str, usage: GrindUsage) -> tuple[LocChange | None, str]:
|
||||
"""Analyze a single grind usage, returning (result, skip_reason)."""
|
||||
commit = find_grind_introduction_commit(repo, usage.file, usage.decl_name)
|
||||
if commit is None:
|
||||
return None, "born_with_grind"
|
||||
|
||||
parent = run_git_safe(["rev-parse", f"{commit}^"], repo)
|
||||
if parent is None:
|
||||
return None, "no_parent"
|
||||
parent = parent.strip()
|
||||
|
||||
old_loc = extract_proof_loc(repo, usage.file, usage.decl_name, parent)
|
||||
new_loc = extract_proof_loc(repo, usage.file, usage.decl_name, "master")
|
||||
|
||||
if old_loc is None:
|
||||
return None, "old_loc_failed"
|
||||
if new_loc is None:
|
||||
return None, "new_loc_failed"
|
||||
|
||||
commit_date = get_commit_date(repo, commit)
|
||||
|
||||
return LocChange(
|
||||
file=usage.file,
|
||||
decl_name=usage.decl_name,
|
||||
decl_type=usage.decl_type,
|
||||
old_loc=old_loc,
|
||||
new_loc=new_loc,
|
||||
loc_saved=old_loc - new_loc,
|
||||
commit_sha=commit[:12],
|
||||
commit_date=commit_date
|
||||
), "success"
|
||||
|
||||
|
||||
def main(repo: str = "."):
|
||||
print("Finding grind usages in master...", file=sys.stderr)
|
||||
usages, total_grind_calls, grind_in_decls = find_grind_usages(repo)
|
||||
print(f"Found {len(usages)} declarations using grind ({grind_in_decls}/{total_grind_calls} grind calls)", file=sys.stderr)
|
||||
|
||||
print("Analyzing git history (this may take a while)...", file=sys.stderr)
|
||||
results: list[LocChange] = []
|
||||
skip_reasons: dict[str, int] = {}
|
||||
|
||||
with ThreadPoolExecutor(max_workers=64) as executor:
|
||||
futures = {executor.submit(analyze_usage_detailed, repo, usage): usage for usage in usages}
|
||||
|
||||
for i, future in enumerate(as_completed(futures)):
|
||||
if (i + 1) % 50 == 0:
|
||||
print(f" Progress: {i + 1}/{len(usages)}", file=sys.stderr, flush=True)
|
||||
|
||||
result, reason = future.result()
|
||||
if result:
|
||||
results.append(result)
|
||||
else:
|
||||
skip_reasons[reason] = skip_reasons.get(reason, 0) + 1
|
||||
|
||||
total_skipped = sum(skip_reasons.values())
|
||||
print(f"\nAnalyzed {len(results)} declarations, skipped {total_skipped}:", file=sys.stderr)
|
||||
for reason, count in sorted(skip_reasons.items(), key=lambda x: -x[1]):
|
||||
print(f" - {reason}: {count}", file=sys.stderr)
|
||||
|
||||
# Sort by LoC saved (descending)
|
||||
results.sort(key=lambda r: r.loc_saved, reverse=True)
|
||||
|
||||
# Output CSV
|
||||
writer = csv.writer(sys.stdout)
|
||||
writer.writerow(["file", "declaration", "type", "old_loc", "new_loc", "loc_saved", "commit", "date"])
|
||||
for r in results:
|
||||
writer.writerow([r.file, r.decl_name, r.decl_type, r.old_loc, r.new_loc, r.loc_saved, r.commit_sha, r.commit_date])
|
||||
|
||||
# Summary stats to stderr
|
||||
total_old = sum(r.old_loc for r in results) if results else 0
|
||||
total_new = sum(r.new_loc for r in results) if results else 0
|
||||
total_saved = sum(r.loc_saved for r in results) if results else 0
|
||||
avg_saved = total_saved / len(results) if results else 0
|
||||
|
||||
print("\n" + "=" * 60, file=sys.stderr)
|
||||
print("GRIND ADOPTION LOC ANALYSIS", file=sys.stderr)
|
||||
print("=" * 60, file=sys.stderr)
|
||||
|
||||
print("\n## Declaration Counts\n", file=sys.stderr)
|
||||
print(f" Total grind tactic calls: {total_grind_calls}", file=sys.stderr)
|
||||
print(f" In named declarations: {grind_in_decls} ({total_grind_calls - grind_in_decls} in anonymous/other)", file=sys.stderr)
|
||||
print(f" Unique declarations: {len(usages)}", file=sys.stderr)
|
||||
print(f" Converted to grind: {len(results)}", file=sys.stderr)
|
||||
print(f" Born with grind: {skip_reasons.get('born_with_grind', 0)}", file=sys.stderr)
|
||||
if skip_reasons.get('old_loc_failed', 0) > 0:
|
||||
print(f" Could not trace history: {skip_reasons.get('old_loc_failed', 0)}", file=sys.stderr)
|
||||
|
||||
print("\n## Lines of Code Impact\n", file=sys.stderr)
|
||||
print(f" Total LoC before grind: {total_old}", file=sys.stderr)
|
||||
print(f" Total LoC after grind: {total_new}", file=sys.stderr)
|
||||
print(f" Total LoC saved: {total_saved}", file=sys.stderr)
|
||||
print(f" Average LoC saved per theorem: {avg_saved:.1f}", file=sys.stderr)
|
||||
big_savings = sum(1 for r in results if r.loc_saved >= 10)
|
||||
print(f" Declarations shrunk by 10+ lines: {big_savings}", file=sys.stderr)
|
||||
|
||||
if results:
|
||||
print("\n## Top 10 Biggest LoC Savings\n", file=sys.stderr)
|
||||
for r in results[:10]:
|
||||
print(f" {r.loc_saved:+4d} lines: {r.decl_name} ({r.file})", file=sys.stderr)
|
||||
|
||||
# Show any that got bigger (negative savings)
|
||||
got_bigger = [r for r in results if r.loc_saved < 0]
|
||||
if got_bigger:
|
||||
print(f"\n## Declarations That Got Bigger ({len(got_bigger)} total)\n", file=sys.stderr)
|
||||
print(" (showing 5 worst):", file=sys.stderr)
|
||||
for r in got_bigger[-5:]: # Show worst 5
|
||||
print(f" {r.loc_saved:+4d} lines: {r.decl_name} ({r.file})", file=sys.stderr)
|
||||
|
||||
print("\n" + "=" * 60, file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="Analyze grind LoC savings")
|
||||
parser.add_argument("--repo", "-r", default=".", help="Repository path")
|
||||
args = parser.parse_args()
|
||||
main(args.repo)
|
||||
@@ -1,127 +0,0 @@
|
||||
/- Examples from the paper "grind: An SMT-Inspired Tactic for Lean 4" -/
|
||||
open Lean Grind
|
||||
|
||||
/- Congruence closure. -/
|
||||
|
||||
example (f : Nat → Nat) (h : a = b) : f (f b) = f (f a) := by grind
|
||||
|
||||
/-
|
||||
E-matching.
|
||||
|
||||
Any `f` that is the left inverse of `g` would work on this example.
|
||||
-/
|
||||
def f (x : Nat) := x - 1
|
||||
def g (x : Nat) := x + 1
|
||||
|
||||
@[grind =] theorem fg : f (g x) = x := by simp [f, g]
|
||||
example : f a = b → a = g c → b = c := by grind
|
||||
|
||||
/-
|
||||
Any `R` that is transitive and symmetric would work on this example.
|
||||
-/
|
||||
def R : Nat → Nat → Prop := (· % 7 = · % 7)
|
||||
@[grind →] theorem Rtrans : R x y → R y z → R x z := by grind [R]
|
||||
@[grind →] theorem Rsymm : R x y → R y x := by grind [R]
|
||||
example : R a b → R c b → R d c → R a d := by grind
|
||||
|
||||
/- Big step operational semantics example. -/
|
||||
|
||||
abbrev Variable := String
|
||||
def State := Variable → Nat
|
||||
inductive Stmt : Type where
|
||||
| skip : Stmt
|
||||
| assign : Variable → (State → Nat) → Stmt
|
||||
| seq : Stmt → Stmt → Stmt
|
||||
| ifThenElse : (State → Prop) → Stmt → Stmt → Stmt
|
||||
| whileDo : (State → Prop) → Stmt → Stmt
|
||||
infix:60 ";; " => Stmt.seq
|
||||
export Stmt (skip assign seq ifThenElse whileDo)
|
||||
set_option quotPrecheck false in
|
||||
notation s:70 "[" x:70 "↦" n:70 "]" => (fun v ↦ if v = x then n else s v)
|
||||
inductive BigStep : Stmt → State → State → Prop where
|
||||
| skip (s : State) : BigStep skip s s
|
||||
| assign (x : Variable) (a : State → Nat) (s : State) : BigStep (assign x a) s (s[x ↦ a s])
|
||||
| seq {S T : Stmt} {s t u : State} (hS : BigStep S s t) (hT : BigStep T t u) :
|
||||
BigStep (S;; T) s u
|
||||
| if_true {B : State → Prop} {s t : State} (hcond : B s) (S T : Stmt) (hbody : BigStep S s t) :
|
||||
BigStep (ifThenElse B S T) s t
|
||||
| if_false {B : State → Prop} {s t : State} (hcond : ¬ B s) (S T : Stmt) (hbody : BigStep T s t) :
|
||||
BigStep (ifThenElse B S T) s t
|
||||
| while_true {B S s t u} (hcond : B s) (hbody : BigStep S s t) (hrest : BigStep (whileDo B S) t u) :
|
||||
BigStep (whileDo B S) s u
|
||||
| while_false {B S s} (hcond : ¬ B s) : BigStep (whileDo B S) s s
|
||||
notation:55 "(" S:55 "," s:55 ")" " ==> " t:55 => BigStep S s t
|
||||
|
||||
example {B S T s t} (hcond : B s) : (ifThenElse B S T, s) ==> t → (S, s) ==> t := by
|
||||
grind [cases BigStep]
|
||||
|
||||
theorem cases_if_of_true {B S T s t} (hcond : B s) : (ifThenElse B S T, s) ==> t → (S, s) ==> t := by
|
||||
grind [cases BigStep]
|
||||
|
||||
theorem cases_if_of_false {B S T s t} (hcond : ¬ B s) : (ifThenElse B S T, s) ==> t → (T, s) ==> t := by
|
||||
grind [cases BigStep]
|
||||
|
||||
example {B S T s t} : (ifThenElse B S T, s) ==> t ↔ (B s ∧ (S, s) ==> t) ∨ (¬ B s ∧ (T, s) ==> t) := by
|
||||
grind [BigStep] -- shortcut for `cases BigStep` and `intro BigStep`
|
||||
|
||||
attribute [grind] BigStep
|
||||
theorem if_iff {B S T s t} : (ifThenElse B S T, s) ==>
|
||||
t ↔ (B s ∧ (S, s) ==> t) ∨ (¬ B s ∧ (T, s) ==> t) := by grind
|
||||
|
||||
/- Dependent pattern matching. -/
|
||||
|
||||
inductive Vec (α : Type u) : Nat → Type u
|
||||
| nil : Vec α 0
|
||||
| cons : α → Vec α n → Vec α (n+1)
|
||||
@[grind =] def Vec.head : Vec α (n+1) → α
|
||||
| .cons a _ => a
|
||||
example (as bs : Vec Int (n+1)) : as.head = bs.head
|
||||
→ (match as, bs with
|
||||
| .cons a _, .cons b _ => a + b) = 2 * as.head := by grind
|
||||
|
||||
/- Theory solvers. -/
|
||||
|
||||
example [CommRing α] (a b c : α) :
|
||||
a + b + c = 3 →
|
||||
a^2 + b^2 + c^2 = 5 →
|
||||
a^3 + b^3 + c^3 = 7 →
|
||||
a^4 + b^4 + c^4 = 9 := by grind
|
||||
|
||||
example (x : BitVec 8) : (x - 16) * (x + 16) = x^2 := by grind
|
||||
|
||||
example [CommSemiring α] [AddRightCancel α] (x y : α) :
|
||||
x^2*y = 1 → x*y^2 = y → y*x = 1 := by grind
|
||||
|
||||
example (a b : UInt32) : a ≤ 2 → b ≤ 3 → a + b ≤ 5 := by grind
|
||||
|
||||
example [LE α] [Std.IsLinearPreorder α] (a b c d : α) :
|
||||
a ≤ b → ¬ (c ≤ b) → ¬ (d ≤ c) → a ≤ d := by grind
|
||||
|
||||
/- Theory combination. -/
|
||||
|
||||
example [CommRing α] [NoNatZeroDivisors α]
|
||||
(a b c : α) (f : α → Nat) :
|
||||
a + b + c = 3 → a^2 + b^2 + c^2 = 5 → a^3 + b^3 + c^3 = 7 →
|
||||
f (a^4 + b^4) + f (9 - c^4) ≠ 1 := by grind
|
||||
|
||||
/- Interactive mode. -/
|
||||
|
||||
-- Remark: Mathlib contains the definition of `Real`, `sin`, and `cos`.
|
||||
axiom Real : Type
|
||||
instance : Lean.Grind.CommRing Real := sorry
|
||||
|
||||
axiom cos : Real → Real
|
||||
axiom sin : Real → Real
|
||||
axiom trig_identity : ∀ x, (cos x)^2 + (sin x)^2 = 1
|
||||
|
||||
-- Manually specify the patterns for `trig_identity`
|
||||
grind_pattern trig_identity => cos x
|
||||
grind_pattern trig_identity => sin x
|
||||
|
||||
example : (cos x + sin x)^2 = 2 * cos x * sin x + 1 := by
|
||||
grind? -- Provides code action
|
||||
|
||||
example : (cos x + sin x)^2 = 2 * cos x * sin x + 1 := by
|
||||
grind =>
|
||||
instantiate only [trig_identity]
|
||||
ring
|
||||
@@ -4,16 +4,16 @@ def ack : Nat → Nat → Nat
|
||||
| 0, y => y+1
|
||||
| x+1, 0 => ack x 1
|
||||
| x+1, y+1 => ack x (ack (x+1) y)
|
||||
termination_by x y => (x, y)
|
||||
termination_by ack x y => (x, y)
|
||||
|
||||
def sum (a : Array Int) : Int :=
|
||||
let rec go (i : Nat) :=
|
||||
if _ : i < a.size then
|
||||
if i < a.size then
|
||||
a[i] + go (i+1)
|
||||
else
|
||||
0
|
||||
termination_by a.size - i
|
||||
go 0
|
||||
termination_by go i => a.size - i
|
||||
|
||||
set_option pp.proofs true
|
||||
#print sum.go
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
These examples are checked in Lean's CI to ensure that they continue
|
||||
to work. They are included in the documentation section of the Lean
|
||||
website via a script that copies the latest version, in order to
|
||||
ensure that the website tracks Lean releases rather than `master`.
|
||||
@@ -179,7 +179,7 @@ local macro "have_eq " lhs:term:max rhs:term:max : tactic =>
|
||||
`(tactic|
|
||||
(have h : $lhs = $rhs :=
|
||||
-- TODO: replace with linarith
|
||||
by simp +arith at *; apply Nat.le_antisymm <;> assumption
|
||||
by simp_arith at *; apply Nat.le_antisymm <;> assumption
|
||||
try subst $lhs))
|
||||
|
||||
/-!
|
||||
@@ -277,13 +277,14 @@ theorem BinTree.find_insert (b : BinTree β) (k : Nat) (v : β)
|
||||
. by_cases' key < k
|
||||
cases h; apply ihr; assumption
|
||||
|
||||
theorem BinTree.find_insert_of_ne (b : BinTree β) (ne : k ≠ k') (v : β)
|
||||
theorem BinTree.find_insert_of_ne (b : BinTree β) (h : k ≠ k') (v : β)
|
||||
: (b.insert k v).find? k' = b.find? k' := by
|
||||
let ⟨t, h⟩ := b; simp
|
||||
induction t with simp
|
||||
| leaf =>
|
||||
intro le
|
||||
exact Nat.lt_of_le_of_ne le ne
|
||||
split <;> (try simp) <;> split <;> (try simp)
|
||||
have_eq k k'
|
||||
contradiction
|
||||
| node left key value right ihl ihr =>
|
||||
let .node hl hr bl br := h
|
||||
specialize ihl bl
|
||||
|
||||
5
doc/examples/bintree.lean.md
Normal file
5
doc/examples/bintree.lean.md
Normal file
@@ -0,0 +1,5 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include bintree.lean}}
|
||||
```
|
||||
@@ -1,2 +0,0 @@
|
||||
Tree.node (Tree.node (Tree.leaf) 1 "one" (Tree.leaf)) 2 "two" (Tree.node (Tree.leaf) 3 "three" (Tree.leaf))
|
||||
[(1, "one"), (2, "two"), (3, "three")]
|
||||
1
doc/examples/compiler/.gitignore
vendored
1
doc/examples/compiler/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
build
|
||||
@@ -1,4 +0,0 @@
|
||||
leanmake --always-make bin
|
||||
|
||||
capture ./build/bin/test hello world
|
||||
check_out_contains "[hello, world]"
|
||||
@@ -1 +0,0 @@
|
||||
[hello, world]
|
||||
@@ -149,7 +149,7 @@ We now define the constant folding optimization that traverses a term if replace
|
||||
/-!
|
||||
The correctness of the `Term.constFold` is proved using induction, case-analysis, and the term simplifier.
|
||||
We prove all cases but the one for `plus` using `simp [*]`. This tactic instructs the term simplifier to
|
||||
use hypotheses such as `a = b` as rewriting/simplifications rules.
|
||||
use hypotheses such as `a = b` as rewriting/simplications rules.
|
||||
We use the `split` to break the nested `match` expression in the `plus` case into two cases.
|
||||
The local variables `iha` and `ihb` are the induction hypotheses for `a` and `b`.
|
||||
The modifier `←` in a term simplifier argument instructs the term simplifier to use the equation as a rewriting rule in
|
||||
|
||||
5
doc/examples/deBruijn.lean.md
Normal file
5
doc/examples/deBruijn.lean.md
Normal file
@@ -0,0 +1,5 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include deBruijn.lean}}
|
||||
```
|
||||
@@ -12,17 +12,17 @@ Remark: this example is based on an example found in the Idris manual.
|
||||
Vectors
|
||||
--------
|
||||
|
||||
A `Vec` is a list of size `n` whose elements belong to a type `α`.
|
||||
A `Vector` is a list of size `n` whose elements belong to a type `α`.
|
||||
-/
|
||||
|
||||
inductive Vec (α : Type u) : Nat → Type u
|
||||
| nil : Vec α 0
|
||||
| cons : α → Vec α n → Vec α (n+1)
|
||||
inductive Vector (α : Type u) : Nat → Type u
|
||||
| nil : Vector α 0
|
||||
| cons : α → Vector α n → Vector α (n+1)
|
||||
|
||||
/-!
|
||||
We can overload the `List.cons` notation `::` and use it to create `Vec`s.
|
||||
We can overload the `List.cons` notation `::` and use it to create `Vector`s.
|
||||
-/
|
||||
infix:67 " :: " => Vec.cons
|
||||
infix:67 " :: " => Vector.cons
|
||||
|
||||
/-!
|
||||
Now, we define the types of our simple functional language.
|
||||
@@ -50,11 +50,11 @@ the builtin instance for `Add Int` as the solution.
|
||||
/-!
|
||||
Expressions are indexed by the types of the local variables, and the type of the expression itself.
|
||||
-/
|
||||
inductive HasType : Fin n → Vec Ty n → Ty → Type where
|
||||
inductive HasType : Fin n → Vector Ty n → Ty → Type where
|
||||
| stop : HasType 0 (ty :: ctx) ty
|
||||
| pop : HasType k ctx ty → HasType k.succ (u :: ctx) ty
|
||||
|
||||
inductive Expr : Vec Ty n → Ty → Type where
|
||||
inductive Expr : Vector Ty n → Ty → Type where
|
||||
| var : HasType i ctx ty → Expr ctx ty
|
||||
| val : Int → Expr ctx Ty.int
|
||||
| lam : Expr (a :: ctx) ty → Expr ctx (Ty.fn a ty)
|
||||
@@ -102,8 +102,8 @@ indexed over the types in scope. Since an environment is just another form of li
|
||||
to the vector of local variable types, we overload again the notation `::` so that we can use the usual list syntax.
|
||||
Given a proof that a variable is defined in the context, we can then produce a value from the environment.
|
||||
-/
|
||||
inductive Env : Vec Ty n → Type where
|
||||
| nil : Env Vec.nil
|
||||
inductive Env : Vector Ty n → Type where
|
||||
| nil : Env Vector.nil
|
||||
| cons : Ty.interp a → Env ctx → Env (a :: ctx)
|
||||
|
||||
infix:67 " :: " => Env.cons
|
||||
@@ -149,4 +149,4 @@ def fact : Expr ctx (Ty.fn Ty.int Ty.int) :=
|
||||
(op (·*·) (delay fun _ => app fact (op (·-·) (var stop) (val 1))) (var stop)))
|
||||
decreasing_by sorry
|
||||
|
||||
#eval! fact.interp Env.nil 10
|
||||
#eval fact.interp Env.nil 10
|
||||
|
||||
5
doc/examples/interp.lean.md
Normal file
5
doc/examples/interp.lean.md
Normal file
@@ -0,0 +1,5 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include interp.lean}}
|
||||
```
|
||||
@@ -1,3 +0,0 @@
|
||||
30
|
||||
interp.lean:146:4: warning: declaration uses `sorry`
|
||||
3628800
|
||||
@@ -94,8 +94,10 @@ theorem List.palindrome_of_eq_reverse (h : as.reverse = as) : Palindrome as := b
|
||||
next => exact Palindrome.nil
|
||||
next a => exact Palindrome.single a
|
||||
next a b as ih =>
|
||||
obtain ⟨rfl, h, -⟩ := by simpa using h
|
||||
exact Palindrome.sandwich b (ih h)
|
||||
have : a = b := by simp_all
|
||||
subst this
|
||||
have : as.reverse = as := by simp_all
|
||||
exact Palindrome.sandwich a (ih this)
|
||||
|
||||
/-!
|
||||
We now define a function that returns `true` iff `as` is a palindrome.
|
||||
|
||||
5
doc/examples/palindromes.lean.md
Normal file
5
doc/examples/palindromes.lean.md
Normal file
@@ -0,0 +1,5 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include palindromes.lean}}
|
||||
```
|
||||
@@ -1,2 +0,0 @@
|
||||
true
|
||||
false
|
||||
@@ -225,7 +225,7 @@ We now define the constant folding optimization that traverses a term if replace
|
||||
/-!
|
||||
The correctness of the `constFold` is proved using induction, case-analysis, and the term simplifier.
|
||||
We prove all cases but the one for `plus` using `simp [*]`. This tactic instructs the term simplifier to
|
||||
use hypotheses such as `a = b` as rewriting/simplifications rules.
|
||||
use hypotheses such as `a = b` as rewriting/simplications rules.
|
||||
We use the `split` to break the nested `match` expression in the `plus` case into two cases.
|
||||
The local variables `iha` and `ihb` are the induction hypotheses for `a` and `b`.
|
||||
The modifier `←` in a term simplifier argument instructs the term simplifier to use the equation as a rewriting rule in
|
||||
|
||||
5
doc/examples/phoas.lean.md
Normal file
5
doc/examples/phoas.lean.md
Normal file
@@ -0,0 +1,5 @@
|
||||
(this example is rendered by Alectryon in the CI)
|
||||
|
||||
```lean
|
||||
{{#include phoas.lean}}
|
||||
```
|
||||
@@ -1,2 +0,0 @@
|
||||
"(((fun x_1 => (fun x_2 => (x_1 + x_2))) 1) 2)"
|
||||
"((((fun x_1 => (fun x_2 => (x_1 + x_2))) 1) 2) + 5)"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user