mirror of
https://github.com/leanprover/lean4.git
synced 2026-04-02 18:24:12 +00:00
Compare commits
46 Commits
sofia/open
...
joachim/ch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
480b2bffc5 | ||
|
|
908b647be9 | ||
|
|
9beb4dad84 | ||
|
|
861bc19e0c | ||
|
|
8f1c18d9f4 | ||
|
|
097f3ebdbc | ||
|
|
861f722844 | ||
|
|
eac9315962 | ||
|
|
29bd37591c | ||
|
|
8b52f4e8f7 | ||
|
|
e2937ad233 | ||
|
|
402a6096b9 | ||
|
|
978bde4a0f | ||
|
|
8aa0c21bf8 | ||
|
|
1aa860af33 | ||
|
|
cdd982a030 | ||
|
|
f11d137a30 | ||
|
|
66924cc4ac | ||
|
|
fc0cf68539 | ||
|
|
46a0a0eb59 | ||
|
|
916004bd3c | ||
|
|
a145b9c11a | ||
|
|
67b6e815b9 | ||
|
|
33c3604b87 | ||
|
|
7488a1f712 | ||
|
|
ffd0f7de85 | ||
|
|
504e099c5d | ||
|
|
17795b02ee | ||
|
|
4f7de4a1d9 | ||
|
|
48800e438c | ||
|
|
f395593ffc | ||
|
|
a88f81bc28 | ||
|
|
313abdb49f | ||
|
|
f08983bf01 | ||
|
|
22308dbaaa | ||
|
|
51e87865c5 | ||
|
|
75ec8e42c8 | ||
|
|
9fc62b7042 | ||
|
|
583c223b16 | ||
|
|
ccc7157c08 | ||
|
|
05046dc3d7 | ||
|
|
43f18fd502 | ||
|
|
b06eb981a3 | ||
|
|
f72137f53a | ||
|
|
96dbc324f3 | ||
|
|
d6e69649b6 |
@@ -7,6 +7,11 @@ 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.
|
||||
|
||||
To rebuild individual modules without a full build, use Lake directly:
|
||||
```
|
||||
cd src && lake build Init.Prelude
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
See `tests/README.md` for full documentation. Quick reference:
|
||||
@@ -56,6 +61,11 @@ make -C build/release/stage2 clean-stdlib
|
||||
```
|
||||
must be run manually before building.
|
||||
|
||||
To rebuild individual stage 2 modules without a full `make stage2`, use Lake directly:
|
||||
```
|
||||
cd build/release/stage2 && lake build Init.Prelude
|
||||
```
|
||||
|
||||
## New features
|
||||
|
||||
When asked to implement new features:
|
||||
|
||||
@@ -157,6 +157,16 @@ Note: `gh pr checks --watch` exits as soon as ALL checks complete (pass or fail)
|
||||
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.
|
||||
|
||||
## Mathlib Bump Branches
|
||||
|
||||
Mathlib `bump/v4.X.0` branches live on the **fork** `leanprover-community/mathlib4-nightly-testing`,
|
||||
NOT on `leanprover-community/mathlib4`.
|
||||
|
||||
## Never Force-Update Remote Refs Without Confirmation
|
||||
|
||||
Never force-update an existing remote branch or tag via `git push --force` or the GitHub API
|
||||
without explicit user confirmation.
|
||||
|
||||
## Error Handling
|
||||
|
||||
**CRITICAL**: If something goes wrong or a command fails:
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -143,7 +143,7 @@ jobs:
|
||||
CMAKE_MAJOR=$(grep -E "^set\(LEAN_VERSION_MAJOR " src/CMakeLists.txt | grep -oE '[0-9]+')
|
||||
CMAKE_MINOR=$(grep -E "^set\(LEAN_VERSION_MINOR " src/CMakeLists.txt | grep -oE '[0-9]+')
|
||||
CMAKE_PATCH=$(grep -E "^set\(LEAN_VERSION_PATCH " src/CMakeLists.txt | grep -oE '[0-9]+')
|
||||
CMAKE_IS_RELEASE=$(grep -m 1 -E "^set\(LEAN_VERSION_IS_RELEASE " src/CMakeLists.txt | sed -nE 's/^set\(LEAN_VERSION_IS_RELEASE ([0-9]+)\).*/\1/p')
|
||||
CMAKE_IS_RELEASE=$(grep -m 1 -E "^set\(LEAN_VERSION_IS_RELEASE " src/CMakeLists.txt | grep -oE '[0-9]+' | head -1)
|
||||
|
||||
# Expected values from tag parsing
|
||||
TAG_MAJOR="${{ steps.set-release.outputs.LEAN_VERSION_MAJOR }}"
|
||||
|
||||
@@ -6,6 +6,6 @@ vscode:
|
||||
- leanprover.lean4
|
||||
|
||||
tasks:
|
||||
- name: Release build
|
||||
init: cmake --preset release
|
||||
- name: Build
|
||||
init: cmake --preset dev
|
||||
command: make -C build/release -j$(nproc || sysctl -n hw.logicalcpu)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
include(ExternalProject)
|
||||
include(FetchContent)
|
||||
|
||||
if(NOT CMAKE_GENERATOR MATCHES "Makefiles")
|
||||
message(FATAL_ERROR "Only makefile generators are supported")
|
||||
@@ -34,7 +36,6 @@ foreach(var ${vars})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
include(ExternalProject)
|
||||
project(LEAN CXX C)
|
||||
|
||||
if(NOT (DEFINED STAGE0_CMAKE_EXECUTABLE_SUFFIX))
|
||||
@@ -119,17 +120,16 @@ if(NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
|
||||
endif()
|
||||
|
||||
if(USE_MIMALLOC)
|
||||
ExternalProject_Add(
|
||||
FetchContent_Declare(
|
||||
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 ""
|
||||
# Unnecessarily deep directory structure, but it saves us from a complicated
|
||||
# stage0 update for now. If we ever update the other dependencies like
|
||||
# cadical, it might be worth reorganizing the directory structure.
|
||||
SOURCE_DIR "${CMAKE_BINARY_DIR}/mimalloc/src/mimalloc"
|
||||
)
|
||||
list(APPEND EXTRA_DEPENDS mimalloc)
|
||||
FetchContent_MakeAvailable(mimalloc)
|
||||
endif()
|
||||
|
||||
if(NOT STAGE1_PREV_STAGE)
|
||||
|
||||
@@ -8,16 +8,26 @@
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "release",
|
||||
"displayName": "Default development optimized build config",
|
||||
"displayName": "Release build config",
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build/release"
|
||||
},
|
||||
{
|
||||
"name": "dev",
|
||||
"displayName": "Default development optimized build config",
|
||||
"cacheVariables": {
|
||||
"STRIP_BINARIES": "OFF"
|
||||
},
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build/dev"
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"displayName": "Debug build config",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"LEAN_EXTRA_CXX_FLAGS": "-DLEAN_DEFAULT_THREAD_STACK_SIZE=16*1024*1024",
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
"STRIP_BINARIES": "OFF"
|
||||
},
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build/debug"
|
||||
@@ -26,7 +36,8 @@
|
||||
"name": "reldebug",
|
||||
"displayName": "Release with assertions enabled",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "RelWithAssert"
|
||||
"CMAKE_BUILD_TYPE": "RelWithAssert",
|
||||
"STRIP_BINARIES": "OFF"
|
||||
},
|
||||
"generator": "Unix Makefiles",
|
||||
"binaryDir": "${sourceDir}/build/reldebug"
|
||||
@@ -38,6 +49,7 @@
|
||||
"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",
|
||||
"STRIP_BINARIES": "OFF",
|
||||
"SMALL_ALLOCATOR": "OFF",
|
||||
"USE_MIMALLOC": "OFF",
|
||||
"BSYMBOLIC": "OFF",
|
||||
@@ -58,6 +70,10 @@
|
||||
"name": "release",
|
||||
"configurePreset": "release"
|
||||
},
|
||||
{
|
||||
"name": "dev",
|
||||
"configurePreset": "dev"
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"configurePreset": "debug"
|
||||
@@ -81,6 +97,11 @@
|
||||
"configurePreset": "release",
|
||||
"output": {"outputOnFailure": true, "shortProgress": true}
|
||||
},
|
||||
{
|
||||
"name": "dev",
|
||||
"configurePreset": "dev",
|
||||
"output": {"outputOnFailure": true, "shortProgress": true}
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"configurePreset": "debug",
|
||||
|
||||
@@ -30,6 +30,9 @@ cd lean4
|
||||
cmake --preset release
|
||||
make -C build/release -j$(nproc || sysctl -n hw.logicalcpu)
|
||||
```
|
||||
|
||||
For development, `cmake --preset dev` is recommended instead.
|
||||
|
||||
You can replace `$(nproc || sysctl -n hw.logicalcpu)` with the desired parallelism amount.
|
||||
|
||||
The above commands will compile the Lean library and binaries into the
|
||||
|
||||
@@ -311,16 +311,16 @@ def check_cmake_version(repo_url, branch, version_major, version_minor, github_t
|
||||
print(f" ❌ Could not retrieve {cmake_file_path} from {branch}")
|
||||
return False
|
||||
|
||||
expected_lines = [
|
||||
f"set(LEAN_VERSION_MAJOR {version_major})",
|
||||
f"set(LEAN_VERSION_MINOR {version_minor})",
|
||||
f"set(LEAN_VERSION_PATCH 0)",
|
||||
f"set(LEAN_VERSION_IS_RELEASE 1)"
|
||||
expected_patterns = [
|
||||
(f"LEAN_VERSION_MAJOR", rf"^set\(LEAN_VERSION_MAJOR\s+{version_major}[\s)]", f"set(LEAN_VERSION_MAJOR {version_major} ...)"),
|
||||
(f"LEAN_VERSION_MINOR", rf"^set\(LEAN_VERSION_MINOR\s+{version_minor}[\s)]", f"set(LEAN_VERSION_MINOR {version_minor} ...)"),
|
||||
(f"LEAN_VERSION_PATCH", rf"^set\(LEAN_VERSION_PATCH\s+0[\s)]", f"set(LEAN_VERSION_PATCH 0 ...)"),
|
||||
(f"LEAN_VERSION_IS_RELEASE", rf"^set\(LEAN_VERSION_IS_RELEASE\s+1[\s)]", f"set(LEAN_VERSION_IS_RELEASE 1 ...)"),
|
||||
]
|
||||
|
||||
for line in expected_lines:
|
||||
if not any(l.strip().startswith(line) for l in content.splitlines()):
|
||||
print(f" ❌ Missing or incorrect line in {cmake_file_path}: {line}")
|
||||
for name, pattern, display in expected_patterns:
|
||||
if not any(re.match(pattern, l.strip()) for l in content.splitlines()):
|
||||
print(f" ❌ Missing or incorrect line in {cmake_file_path}: {display}")
|
||||
return False
|
||||
|
||||
print(f" ✅ CMake version settings are correct in {cmake_file_path}")
|
||||
@@ -343,11 +343,11 @@ def check_stage0_version(repo_url, branch, version_major, version_minor, github_
|
||||
for line in content.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("set(LEAN_VERSION_MAJOR "):
|
||||
actual = stripped.split()[-1].rstrip(")")
|
||||
actual = stripped.split()[1].rstrip(")")
|
||||
if actual != str(version_major):
|
||||
errors.append(f"LEAN_VERSION_MAJOR: expected {version_major}, found {actual}")
|
||||
elif stripped.startswith("set(LEAN_VERSION_MINOR "):
|
||||
actual = stripped.split()[-1].rstrip(")")
|
||||
actual = stripped.split()[1].rstrip(")")
|
||||
if actual != str(version_minor):
|
||||
errors.append(f"LEAN_VERSION_MINOR: expected {version_minor}, found {actual}")
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ endif()
|
||||
include(ExternalProject)
|
||||
project(LEAN CXX C)
|
||||
set(LEAN_VERSION_MAJOR 4 CACHE STRING "")
|
||||
set(LEAN_VERSION_MINOR 30 CACHE STRING "")
|
||||
set(LEAN_VERSION_MINOR 31 CACHE STRING "")
|
||||
set(LEAN_VERSION_PATCH 0 CACHE STRING "")
|
||||
set(LEAN_VERSION_IS_RELEASE 0 CACHE STRING "") # This number is 1 in the release revision, and 0 otherwise.
|
||||
set(LEAN_SPECIAL_VERSION_DESC "" CACHE STRING "Additional version description like 'nightly-2018-03-11'")
|
||||
@@ -80,6 +80,7 @@ option(CCACHE "use ccache" ON)
|
||||
option(SPLIT_STACK "SPLIT_STACK" OFF)
|
||||
# When OFF we disable LLVM support
|
||||
option(LLVM "LLVM" OFF)
|
||||
option(STRIP_BINARIES "Strip produced binaries" ON)
|
||||
|
||||
# When ON we include githash in the version string
|
||||
option(USE_GITHASH "GIT_HASH" ON)
|
||||
@@ -614,6 +615,38 @@ else()
|
||||
OUTPUT_VARIABLE GIT_SHA1
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
# Fallback for jj workspaces where git cannot find .git directly.
|
||||
# Use `jj git root` to find the backing git repo, then `jj log` to
|
||||
# resolve the current workspace's commit (git HEAD points to the root
|
||||
# workspace, not the current one).
|
||||
if("${GIT_SHA1}" STREQUAL "")
|
||||
find_program(JJ_EXECUTABLE jj)
|
||||
if(JJ_EXECUTABLE)
|
||||
execute_process(
|
||||
COMMAND "${JJ_EXECUTABLE}" git root
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE _jj_git_dir
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
RESULT_VARIABLE _jj_git_root_result
|
||||
)
|
||||
execute_process(
|
||||
COMMAND "${JJ_EXECUTABLE}" log -r @ --no-graph -T "commit_id"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE _jj_commit
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
RESULT_VARIABLE _jj_rev_result
|
||||
)
|
||||
if(_jj_git_root_result EQUAL 0 AND _jj_rev_result EQUAL 0)
|
||||
execute_process(
|
||||
COMMAND git --git-dir "${_jj_git_dir}" ls-tree "${_jj_commit}" stage0 --object-only
|
||||
OUTPUT_VARIABLE GIT_SHA1
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
message(STATUS "stage0 sha1: ${GIT_SHA1}")
|
||||
# Now that we've prepared the information for the next stage, we can forget that we will use
|
||||
# Lake in the future as we won't use it in this stage
|
||||
@@ -797,7 +830,14 @@ if(LLVM AND STAGE GREATER 0)
|
||||
set(EXTRA_LEANMAKE_OPTS "LLVM=1")
|
||||
endif()
|
||||
|
||||
set(STDLIBS Init Std Lean Leanc LeanIR)
|
||||
set(
|
||||
STDLIBS
|
||||
Init
|
||||
Std
|
||||
Lean
|
||||
Leanc
|
||||
LeanIR
|
||||
)
|
||||
if(NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
|
||||
list(APPEND STDLIBS Lake LeanChecker)
|
||||
endif()
|
||||
@@ -905,10 +945,7 @@ if(PREV_STAGE)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten")
|
||||
add_custom_target(leanir ALL
|
||||
DEPENDS leanshared
|
||||
COMMAND $(MAKE) -f ${CMAKE_BINARY_DIR}/stdlib.make leanir
|
||||
VERBATIM)
|
||||
add_custom_target(leanir ALL DEPENDS leanshared COMMAND $(MAKE) -f ${CMAKE_BINARY_DIR}/stdlib.make leanir VERBATIM)
|
||||
endif()
|
||||
|
||||
# use Bash version for building, use Lean version in bin/ for tests & distribution
|
||||
|
||||
@@ -20,12 +20,20 @@ universe u
|
||||
|
||||
namespace ByteArray
|
||||
|
||||
deriving instance BEq for ByteArray
|
||||
@[extern "lean_sarray_dec_eq"]
|
||||
def beq (lhs rhs : @& ByteArray) : Bool :=
|
||||
lhs.data == rhs.data
|
||||
|
||||
instance : BEq ByteArray where
|
||||
beq := beq
|
||||
|
||||
attribute [ext] ByteArray
|
||||
|
||||
instance : DecidableEq ByteArray :=
|
||||
fun _ _ => decidable_of_decidable_of_iff ByteArray.ext_iff.symm
|
||||
@[extern "lean_sarray_dec_eq"]
|
||||
def decEq (lhs rhs : @& ByteArray) : Decidable (lhs = rhs) :=
|
||||
decidable_of_decidable_of_iff ByteArray.ext_iff.symm
|
||||
|
||||
instance : DecidableEq ByteArray := decEq
|
||||
|
||||
instance : Inhabited ByteArray where
|
||||
default := empty
|
||||
|
||||
@@ -9,7 +9,7 @@ prelude
|
||||
public import Init.Data.Order.Ord
|
||||
public import Init.Data.String.Basic
|
||||
import Init.Data.Char.Lemmas
|
||||
import Init.Data.String.Lemmas
|
||||
import Init.Data.String.Lemmas.StringOrder
|
||||
|
||||
public section
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Std
|
||||
/--
|
||||
Appends all the elements in the iterator, in order.
|
||||
-/
|
||||
@[inline]
|
||||
public def Iter.joinString {α β : Type} [Iterator α Id β] [ToString β]
|
||||
(it : Std.Iter (α := α) β) : String :=
|
||||
(it.map toString).fold (init := "") (· ++ ·)
|
||||
|
||||
@@ -20,49 +20,4 @@ public import Init.Data.String.Lemmas.Intercalate
|
||||
public import Init.Data.String.Lemmas.Iter
|
||||
public import Init.Data.String.Lemmas.Hashable
|
||||
public import Init.Data.String.Lemmas.TakeDrop
|
||||
import Init.Data.Order.Lemmas
|
||||
public import Init.Data.String.Basic
|
||||
import Init.Data.Char.Lemmas
|
||||
import Init.Data.Char.Order
|
||||
import Init.Data.List.Lex
|
||||
|
||||
public section
|
||||
|
||||
open Std
|
||||
|
||||
namespace String
|
||||
|
||||
@[deprecated toList_inj (since := "2025-10-30")]
|
||||
protected theorem data_eq_of_eq {a b : String} (h : a = b) : a.toList = b.toList :=
|
||||
h ▸ rfl
|
||||
@[deprecated toList_inj (since := "2025-10-30")]
|
||||
protected theorem ne_of_data_ne {a b : String} (h : a.toList ≠ b.toList) : a ≠ b := by
|
||||
simpa [← toList_inj]
|
||||
|
||||
@[simp] protected theorem not_le {a b : String} : ¬ a ≤ b ↔ b < a := Decidable.not_not
|
||||
@[simp] protected theorem not_lt {a b : String} : ¬ a < b ↔ b ≤ a := Iff.rfl
|
||||
@[simp] protected theorem le_refl (a : String) : a ≤ a := List.le_refl _
|
||||
@[simp] protected theorem lt_irrefl (a : String) : ¬ a < a := List.lt_irrefl _
|
||||
|
||||
attribute [local instance] Char.notLTTrans Char.ltTrichotomous Char.ltAsymm
|
||||
|
||||
protected theorem le_trans {a b c : String} : a ≤ b → b ≤ c → a ≤ c := List.le_trans
|
||||
protected theorem lt_trans {a b c : String} : a < b → b < c → a < c := List.lt_trans
|
||||
protected theorem le_total (a b : String) : a ≤ b ∨ b ≤ a := List.le_total _ _
|
||||
protected theorem le_antisymm {a b : String} : a ≤ b → b ≤ a → a = b := fun h₁ h₂ => String.ext (List.le_antisymm (as := a.toList) (bs := b.toList) h₁ h₂)
|
||||
protected theorem lt_asymm {a b : String} (h : a < b) : ¬ b < a := List.lt_asymm h
|
||||
protected theorem ne_of_lt {a b : String} (h : a < b) : a ≠ b := by
|
||||
have := String.lt_irrefl a
|
||||
intro h; subst h; contradiction
|
||||
|
||||
instance instIsLinearOrder : IsLinearOrder String := by
|
||||
apply IsLinearOrder.of_le
|
||||
case le_antisymm => constructor; apply String.le_antisymm
|
||||
case le_trans => constructor; apply String.le_trans
|
||||
case le_total => constructor; apply String.le_total
|
||||
|
||||
instance : LawfulOrderLT String where
|
||||
lt_iff a b := by
|
||||
simp [← String.not_le, Decidable.imp_iff_not_or, Std.Total.total]
|
||||
|
||||
end String
|
||||
public import Init.Data.String.Lemmas.StringOrder
|
||||
|
||||
@@ -40,7 +40,7 @@ framework.
|
||||
/--
|
||||
This data-carrying typeclass is used to give semantics to a pattern type that implements
|
||||
{name}`ForwardPattern` and/or {name}`ToForwardSearcher` by providing an abstract, not necessarily
|
||||
decidable {name}`PatternModel.Matches` predicate that implementates of {name}`ForwardPattern`
|
||||
decidable {name}`PatternModel.Matches` predicate that implementations of {name}`ForwardPattern`
|
||||
and {name}`ToForwardSearcher` can be validated against.
|
||||
|
||||
Correctness results for generic functions relying on the pattern infrastructure, for example the
|
||||
@@ -151,7 +151,7 @@ theorem IsLongestMatch.le_of_isMatch {pat : ρ} [PatternModel pat] {s : Slice} {
|
||||
|
||||
/--
|
||||
Predicate stating that the region between the start of the slice {name}`s` and the position
|
||||
{name}`pos` matches the patten {name}`pat`, and that there is no longer match starting at the
|
||||
{name}`pos` matches the pattern {name}`pat`, and that there is no longer match starting at the
|
||||
beginning of the slice. This is what a correct matcher should match.
|
||||
|
||||
In some cases, being a match and being a longest match will coincide, see
|
||||
@@ -228,7 +228,7 @@ theorem isLongestRevMatch_iff_isRevMatch {ρ : Type} (pat : ρ) [PatternModel pa
|
||||
exact ht₅ (NoSuffixPatternModel.eq_empty _ _ ht₂ (ht₅'' ▸ ht₂'))
|
||||
|
||||
/--
|
||||
Predicate stating that the slice formed by {name}`startPos` and {name}`endPos` contains is a match
|
||||
Predicate stating that the slice formed by {name}`startPos` and {name}`endPos` contains a match
|
||||
of {name}`pat` in {name}`s` and it is longest among matches starting at {name}`startPos`.
|
||||
-/
|
||||
structure IsLongestMatchAt (pat : ρ) [PatternModel pat] {s : Slice} (startPos endPos : s.Pos) : Prop where
|
||||
@@ -411,7 +411,7 @@ theorem not_revMatchesAt_startPos {pat : ρ} [PatternModel pat] {s : Slice} :
|
||||
intro h
|
||||
simpa [← Pos.ofSliceTo_inj] using h.ne_endPos
|
||||
|
||||
theorem revMatchesAt_iff_revMatchesAt_ofSliceto {pat : ρ} [PatternModel pat] {s : Slice} {base : s.Pos}
|
||||
theorem revMatchesAt_iff_revMatchesAt_ofSliceTo {pat : ρ} [PatternModel pat] {s : Slice} {base : s.Pos}
|
||||
{pos : (s.sliceTo base).Pos} : RevMatchesAt pat pos ↔ RevMatchesAt pat (Pos.ofSliceTo pos) := by
|
||||
simp only [revMatchesAt_iff_exists_isLongestRevMatchAt]
|
||||
constructor
|
||||
@@ -505,8 +505,8 @@ theorem LawfulForwardPatternModel.skipPrefix?_eq_none_iff {ρ : Type} {pat : ρ}
|
||||
/--
|
||||
Predicate stating compatibility between {name}`PatternModel` and {name}`BackwardPattern`.
|
||||
|
||||
This extends {name}`LawfulForwardPattern`, but it is much stronger because it forces the
|
||||
{name}`ForwardPattern` to match the longest prefix of the given slice that matches the property
|
||||
This extends {name}`LawfulBackwardPattern`, but it is much stronger because it forces the
|
||||
{name}`BackwardPattern` to match the longest prefix of the given slice that matches the property
|
||||
supplied by the {name}`PatternModel` instance.
|
||||
-/
|
||||
class LawfulBackwardPatternModel {ρ : Type} (pat : ρ) [BackwardPattern pat]
|
||||
|
||||
@@ -65,7 +65,7 @@ theorem startsWith_prop_eq_head? {P : Char → Prop} [DecidablePred P] {s : Slic
|
||||
s.startsWith P = s.copy.toList.head?.any (decide <| P ·) := by
|
||||
simp [startsWith_prop_eq_startsWith_decide, startsWith_bool_eq_head?]
|
||||
|
||||
theorem eq_append_of_dropPrefix_prop_eq_some {P : Char → Prop} [DecidablePred P] {s res : Slice} (h : s.dropPrefix? P = some res) :
|
||||
theorem eq_append_of_dropPrefix?_prop_eq_some {P : Char → Prop} [DecidablePred P] {s res : Slice} (h : s.dropPrefix? P = some res) :
|
||||
∃ c, s.copy = singleton c ++ res.copy ∧ P c := by
|
||||
rw [dropPrefix?_prop_eq_dropPrefix?_decide] at h
|
||||
simpa using eq_append_of_dropPrefix?_bool_eq_some h
|
||||
@@ -162,7 +162,7 @@ theorem startsWith_prop_eq_head? {P : Char → Prop} [DecidablePred P] {s : Stri
|
||||
theorem eq_append_of_dropPrefix?_prop_eq_some {P : Char → Prop} [DecidablePred P] {s : String} {res : Slice}
|
||||
(h : s.dropPrefix? P = some res) : ∃ c, s = singleton c ++ res.copy ∧ P c := by
|
||||
rw [dropPrefix?_eq_dropPrefix?_toSlice] at h
|
||||
simpa using Slice.eq_append_of_dropPrefix_prop_eq_some h
|
||||
simpa using Slice.eq_append_of_dropPrefix?_prop_eq_some h
|
||||
|
||||
theorem skipSuffix?_bool_eq_some_iff {p : Char → Bool} {s : String} {pos : s.Pos} :
|
||||
s.skipSuffix? p = some pos ↔ ∃ h, pos = s.endPos.prev h ∧ p ((s.endPos.prev h).get (by simp)) = true := by
|
||||
|
||||
49
src/Init/Data/String/Lemmas/StringOrder.lean
Normal file
49
src/Init/Data/String/Lemmas/StringOrder.lean
Normal file
@@ -0,0 +1,49 @@
|
||||
/-
|
||||
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.String.Basic
|
||||
public import Init.Data.Order.Classes
|
||||
import Init.Data.List.Lex
|
||||
import Init.Data.Char.Lemmas
|
||||
import Init.Data.Char.Order
|
||||
import Init.Data.Order.Factories
|
||||
import Init.Data.Order.Lemmas
|
||||
|
||||
public section
|
||||
|
||||
open Std
|
||||
|
||||
namespace String
|
||||
|
||||
@[simp] protected theorem not_le {a b : String} : ¬ a ≤ b ↔ b < a := Decidable.not_not
|
||||
@[simp] protected theorem not_lt {a b : String} : ¬ a < b ↔ b ≤ a := Iff.rfl
|
||||
@[simp] protected theorem le_refl (a : String) : a ≤ a := List.le_refl _
|
||||
@[simp] protected theorem lt_irrefl (a : String) : ¬ a < a := List.lt_irrefl _
|
||||
|
||||
attribute [local instance] Char.notLTTrans Char.ltTrichotomous Char.ltAsymm
|
||||
|
||||
protected theorem le_trans {a b c : String} : a ≤ b → b ≤ c → a ≤ c := List.le_trans
|
||||
protected theorem lt_trans {a b c : String} : a < b → b < c → a < c := List.lt_trans
|
||||
protected theorem le_total (a b : String) : a ≤ b ∨ b ≤ a := List.le_total _ _
|
||||
protected theorem le_antisymm {a b : String} : a ≤ b → b ≤ a → a = b := fun h₁ h₂ => String.ext (List.le_antisymm (as := a.toList) (bs := b.toList) h₁ h₂)
|
||||
protected theorem lt_asymm {a b : String} (h : a < b) : ¬ b < a := List.lt_asymm h
|
||||
protected theorem ne_of_lt {a b : String} (h : a < b) : a ≠ b := by
|
||||
have := String.lt_irrefl a
|
||||
intro h; subst h; contradiction
|
||||
|
||||
instance instIsLinearOrder : IsLinearOrder String := by
|
||||
apply IsLinearOrder.of_le
|
||||
case le_antisymm => constructor; apply String.le_antisymm
|
||||
case le_trans => constructor; apply String.le_trans
|
||||
case le_total => constructor; apply String.le_total
|
||||
|
||||
instance : LawfulOrderLT String where
|
||||
lt_iff a b := by
|
||||
simp [← String.not_le, Decidable.imp_iff_not_or, Std.Total.total]
|
||||
|
||||
end String
|
||||
@@ -706,14 +706,14 @@ Returns {name}`none` otherwise.
|
||||
This function is generic over all currently supported patterns.
|
||||
-/
|
||||
@[inline]
|
||||
def Pos.revSkip? {s : Slice} (pos : s.Pos) (pat : ρ) [ForwardPattern pat] : Option s.Pos :=
|
||||
((s.sliceFrom pos).skipPrefix? pat).map Pos.ofSliceFrom
|
||||
def Pos.revSkip? {s : Slice} (pos : s.Pos) (pat : ρ) [BackwardPattern pat] : Option s.Pos :=
|
||||
((s.sliceFrom pos).skipSuffix? pat).map Pos.ofSliceFrom
|
||||
|
||||
/--
|
||||
If {name}`pat` matches a suffix of {name}`s`, returns the remainder. Returns {name}`none` otherwise.
|
||||
|
||||
Use {name (scope := "Init.Data.String.Slice")}`String.Slice.dropSuffix` to return the slice
|
||||
unchanged when {name}`pat` does not match a prefix.
|
||||
unchanged when {name}`pat` does not match a suffix.
|
||||
|
||||
This function is generic over all currently supported patterns.
|
||||
|
||||
@@ -775,7 +775,7 @@ def Pos.revSkipWhile {s : Slice} (pos : s.Pos) (pat : ρ) [BackwardPattern pat]
|
||||
termination_by pos.down
|
||||
|
||||
/--
|
||||
Returns the position a the start of the longest suffix of {name}`s` for which {name}`pat` matches
|
||||
Returns the position at the start of the longest suffix of {name}`s` for which {name}`pat` matches
|
||||
(potentially repeatedly).
|
||||
-/
|
||||
@[inline]
|
||||
|
||||
@@ -314,7 +314,7 @@ Returns {name}`none` otherwise.
|
||||
This function is generic over all currently supported patterns.
|
||||
-/
|
||||
@[inline]
|
||||
def Pos.revSkip? {s : String} (pos : s.Pos) (pat : ρ) [ForwardPattern pat] : Option s.Pos :=
|
||||
def Pos.revSkip? {s : String} (pos : s.Pos) (pat : ρ) [BackwardPattern pat] : Option s.Pos :=
|
||||
(pos.toSlice.revSkip? pat).map Pos.ofToSlice
|
||||
|
||||
/--
|
||||
@@ -461,7 +461,7 @@ def dropPrefix? (s : String) (pat : ρ) [ForwardPattern pat] : Option String.Sli
|
||||
If {name}`pat` matches a suffix of {name}`s`, returns the remainder. Returns {name}`none` otherwise.
|
||||
|
||||
Use {name (scope := "Init.Data.String.TakeDrop")}`String.dropSuffix` to return the slice
|
||||
unchanged when {name}`pat` does not match a prefix.
|
||||
unchanged when {name}`pat` does not match a suffix.
|
||||
|
||||
This is a cheap operation because it does not allocate a new string to hold the result.
|
||||
To convert the result into a string, use {name}`String.Slice.copy`.
|
||||
|
||||
@@ -30,13 +30,13 @@ simpMatchDiscrsOnly (match 0 with | 0 => true | _ => false) = true
|
||||
```
|
||||
using `eq_self`.
|
||||
-/
|
||||
def simpMatchDiscrsOnly {α : Sort u} (a : α) : α := a
|
||||
@[expose] def simpMatchDiscrsOnly {α : Sort u} (a : α) : α := a
|
||||
|
||||
/--
|
||||
Gadget for protecting lambda abstractions created by `abstractGroundMismatches?`
|
||||
from beta reduction during preprocessing. See `ProveEq.lean` for details.
|
||||
-/
|
||||
def abstractFn {α : Sort u} (a : α) : α := a
|
||||
@[expose] def abstractFn {α : Sort u} (a : α) : α := a
|
||||
|
||||
/-- Gadget for representing offsets `t+k` in patterns. -/
|
||||
def offset (a b : Nat) : Nat := a + b
|
||||
|
||||
@@ -624,6 +624,23 @@ existing code. It may be removed in a future version of the library.
|
||||
syntax (name := deprecated) "deprecated" (ppSpace ident)? (ppSpace str)?
|
||||
(" (" &"since" " := " str ")")? : attr
|
||||
|
||||
/--
|
||||
The attribute `@[deprecated_arg old new]` marks a named parameter as deprecated.
|
||||
|
||||
When a caller uses the old name with a replacement available, a deprecation warning is emitted
|
||||
and the argument is silently forwarded to the new parameter. When no replacement is provided,
|
||||
the parameter is treated as removed and using it produces an error.
|
||||
|
||||
* `@[deprecated_arg old new (since := "2026-03-18")]` marks `old` as a deprecated alias for `new`.
|
||||
* `@[deprecated_arg old new "use foo instead" (since := "2026-03-18")]` adds a custom message.
|
||||
* `@[deprecated_arg old (since := "2026-03-18")]` marks `old` as a removed parameter (no replacement).
|
||||
* `@[deprecated_arg old "no longer needed" (since := "2026-03-18")]` removed with a custom message.
|
||||
|
||||
A warning is emitted if `(since := "...")` is omitted.
|
||||
-/
|
||||
syntax (name := deprecated_arg) "deprecated_arg" ppSpace ident (ppSpace ident)? (ppSpace str)?
|
||||
(" (" &"since" " := " str ")")? : attr
|
||||
|
||||
/--
|
||||
The attribute `@[suggest_for ..]` on a declaration suggests likely ways in which
|
||||
someone might **incorrectly** refer to a definition.
|
||||
|
||||
@@ -36,9 +36,6 @@ private local instance : ToString Int where
|
||||
private local instance : Repr Int where
|
||||
reprPrec i prec := if i < 0 then Repr.addAppParen (toString i) prec else toString i
|
||||
|
||||
private local instance : Append String where
|
||||
append := String.Internal.append
|
||||
|
||||
/-- Internal representation of a linear combination of atoms, and a constant term. -/
|
||||
structure LinearCombo where
|
||||
/-- Constant term. -/
|
||||
|
||||
@@ -232,6 +232,7 @@ partial def checkCases (c : Cases .pure) : CheckM Unit := do
|
||||
withParams params do check k
|
||||
|
||||
partial def check (code : Code .pure) : CheckM Unit := do
|
||||
checkSystem "LCNF check"
|
||||
match code with
|
||||
| .let decl k => checkLetDecl decl; withFVarId decl.fvarId do check k
|
||||
| .fun decl k =>
|
||||
|
||||
@@ -21,7 +21,7 @@ Within a basic block, it is always safe to:
|
||||
until the later inc) and thus doing all relevant `inc` in the beginning doesn't change
|
||||
semantics.
|
||||
- Move all decrements on a variable to the last `dec` location (summing the counts). Because the
|
||||
value is guaranteed to stay alive until at least the last `dec` anyway so a similiar argument to
|
||||
value is guaranteed to stay alive until at least the last `dec` anyway so a similar argument to
|
||||
`inc` holds.
|
||||
|
||||
Crucially this pass must be placed after `expandResetReuse` as that one relies on `inc`s still being
|
||||
|
||||
@@ -69,8 +69,8 @@ open ImpureType
|
||||
abbrev Mask := Array (Option FVarId)
|
||||
|
||||
/--
|
||||
Try to erase `inc` instructions on projections of `targetId` occuring in the tail of `ds`.
|
||||
Return the updated `ds` and mask contianing the `FVarId`s whose `inc` was removed.
|
||||
Try to erase `inc` instructions on projections of `targetId` occurring in the tail of `ds`.
|
||||
Return the updated `ds` and mask containing the `FVarId`s whose `inc` was removed.
|
||||
-/
|
||||
partial def eraseProjIncFor (nFields : Nat) (targetId : FVarId) (ds : Array (CodeDecl .impure)) :
|
||||
CompilerM (Array (CodeDecl .impure) × Mask) := do
|
||||
|
||||
@@ -188,6 +188,7 @@ where
|
||||
profileitM Exception profilerName (← getOptions) do
|
||||
let mut state : (pu : Purity) × Array (Decl pu) := ⟨inPhase, decls⟩
|
||||
for pass in passes do
|
||||
checkSystem "LCNF compiler"
|
||||
state ← withTraceNode `Compiler (fun _ => return m!"compiler phase: {pass.phase}, pass: {pass.name}") do
|
||||
let decls ← withPhase pass.phase do
|
||||
state.fst.withAssertPurity pass.phase.toPurity fun h => do
|
||||
|
||||
@@ -217,6 +217,8 @@ Simplify `code`
|
||||
-/
|
||||
partial def simp (code : Code .pure) : SimpM (Code .pure) := withIncRecDepth do
|
||||
incVisited
|
||||
if (← get).visited % 512 == 0 then
|
||||
checkSystem "LCNF simp"
|
||||
match code with
|
||||
| .let decl k =>
|
||||
let baseDecl := decl
|
||||
|
||||
@@ -146,7 +146,7 @@ Similar to the default `Lean.withIncRecDepth`, but include the `inlineStack` in
|
||||
@[inline] def withIncRecDepth (x : SimpM α) : SimpM α := do
|
||||
let curr ← MonadRecDepth.getRecDepth
|
||||
let max ← MonadRecDepth.getMaxRecDepth
|
||||
if curr == max then
|
||||
if max != 0 && curr == max then
|
||||
throwMaxRecDepth
|
||||
else
|
||||
MonadRecDepth.withRecDepth (curr+1) x
|
||||
|
||||
@@ -448,13 +448,25 @@ Note that the value of `ctx.initHeartbeats` is ignored and replaced with `IO.get
|
||||
protected def withIncRecDepth [Monad m] [MonadControlT CoreM m] (x : m α) : m α :=
|
||||
controlAt CoreM fun runInBase => withIncRecDepth (runInBase x)
|
||||
|
||||
/--
|
||||
Record a check-in for the `LEAN_CHECK_SYSTEM_INTERVAL_MS` monitoring.
|
||||
When that env var is set, warns on stderr if too much CPU time has elapsed since
|
||||
the last check-in from either the C++ `check_system` or this function.
|
||||
-/
|
||||
@[extern "lean_check_system_interval"]
|
||||
opaque checkSystemInterval (componentName : @& String) : BaseIO Unit
|
||||
|
||||
/--
|
||||
Throws an internal interrupt exception if cancellation has been requested. The exception is not
|
||||
caught by `try catch` but is intended to be caught by `Command.withLoggingExceptions` at the top
|
||||
level of elaboration. In particular, we want to skip producing further incremental snapshots after
|
||||
the exception has been thrown.
|
||||
|
||||
Like `checkSystem` but without the global heartbeat check, for callers that have their own
|
||||
heartbeat tracking (e.g. `SynthInstance`).
|
||||
-/
|
||||
@[inline] def checkInterrupted : CoreM Unit := do
|
||||
checkSystemInterval "Lean elaborator"
|
||||
if let some tk := (← read).cancelTk? then
|
||||
if (← tk.isSet) then
|
||||
throwInterruptException
|
||||
|
||||
@@ -60,7 +60,7 @@ instance : EmptyCollection (Trie α) :=
|
||||
instance : Inhabited (Trie α) where
|
||||
default := empty
|
||||
|
||||
/-- Insert or update the value at a the given key `s`. -/
|
||||
/-- Insert or update the value at the given key `s`. -/
|
||||
partial def upsert (t : Trie α) (s : String) (f : Option α → α) : Trie α :=
|
||||
let rec insertEmpty (i : Nat) : Trie α :=
|
||||
if h : i < s.utf8ByteSize then
|
||||
@@ -100,7 +100,7 @@ partial def upsert (t : Trie α) (s : String) (f : Option α → α) : Trie α :
|
||||
node (f v) cs ts
|
||||
loop 0 t
|
||||
|
||||
/-- Inserts a value at a the given key `s`, overriding an existing value if present. -/
|
||||
/-- Inserts a value at the given key `s`, overriding an existing value if present. -/
|
||||
partial def insert (t : Trie α) (s : String) (val : α) : Trie α :=
|
||||
upsert t s (fun _ => val)
|
||||
|
||||
|
||||
45
src/Lean/DeprecatedModule.lean
Normal file
45
src/Lean/DeprecatedModule.lean
Normal file
@@ -0,0 +1,45 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Wojciech Różowski
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Lean.Compiler.ModPkgExt
|
||||
|
||||
public section
|
||||
|
||||
namespace Lean
|
||||
|
||||
structure DeprecatedModuleEntry where
|
||||
message? : Option String := none
|
||||
since? : Option String := none
|
||||
deriving Inhabited
|
||||
|
||||
register_builtin_option linter.deprecated.module : Bool := {
|
||||
defValue := true
|
||||
descr := "if true, generate warnings when importing deprecated modules"
|
||||
}
|
||||
|
||||
builtin_initialize deprecatedModuleExt : ModuleEnvExtension <| Option DeprecatedModuleEntry ←
|
||||
registerModuleEnvExtension <| pure none
|
||||
|
||||
def Environment.getDeprecatedModuleByIdx? (env : Environment) (idx : ModuleIdx) : Option DeprecatedModuleEntry :=
|
||||
deprecatedModuleExt.getStateByIdx? env idx |>.join
|
||||
|
||||
def Environment.setDeprecatedModule (entry : Option DeprecatedModuleEntry) (env : Environment) : Environment :=
|
||||
deprecatedModuleExt.setState env entry
|
||||
|
||||
def formatDeprecatedModuleWarning (env : Environment) (idx : ModuleIdx) (modName : Name)
|
||||
(entry : DeprecatedModuleEntry) : String :=
|
||||
let msg := entry.message?.getD ""
|
||||
let replacements := env.header.moduleData[idx.toNat]!.imports.filter fun imp =>
|
||||
imp.module != `Init
|
||||
let lines := replacements.foldl (init := "") fun acc imp =>
|
||||
acc ++ s!"import {imp.module}\n"
|
||||
s!"{msg}\n\
|
||||
'{modName}' has been deprecated: please replace this import by\n\n\
|
||||
{lines}"
|
||||
|
||||
end Lean
|
||||
@@ -39,6 +39,7 @@ public import Lean.Elab.Extra
|
||||
public import Lean.Elab.GenInjective
|
||||
public import Lean.Elab.BuiltinTerm
|
||||
public import Lean.Elab.Arg
|
||||
public import Lean.Elab.DeprecatedArg
|
||||
public import Lean.Elab.PatternVar
|
||||
public import Lean.Elab.ElabRules
|
||||
public import Lean.Elab.Macro
|
||||
|
||||
@@ -11,6 +11,7 @@ public import Lean.Elab.Binders
|
||||
public import Lean.Elab.RecAppSyntax
|
||||
public import Lean.IdentifierSuggestion
|
||||
import all Lean.Elab.ErrorUtils
|
||||
import Lean.Elab.DeprecatedArg
|
||||
import Init.Omega
|
||||
|
||||
public section
|
||||
@@ -88,6 +89,38 @@ def synthesizeAppInstMVars (instMVars : Array MVarId) (app : Expr) : TermElabM U
|
||||
private def findBinderName? (namedArgs : List NamedArg) (binderName : Name) : Option NamedArg :=
|
||||
namedArgs.find? fun namedArg => namedArg.name == binderName
|
||||
|
||||
/--
|
||||
If the function being applied is a constant, search `namedArgs` for an argument whose name is
|
||||
a deprecated alias of `binderName`. When `linter.deprecated.arg` is enabled (the default),
|
||||
returns `some namedArg` after emitting a deprecation warning with a code action hint. When the
|
||||
option is disabled, returns `none` (the old name falls through to the normal "invalid argument"
|
||||
error). The returned `namedArg` retains its original (old) name.
|
||||
-/
|
||||
private def findDeprecatedBinderName? (namedArgs : List NamedArg) (f : Expr) (binderName : Name) :
|
||||
TermElabM (Option NamedArg) := do
|
||||
unless linter.deprecated.arg.get <| ← getOptions do return .none
|
||||
unless f.getAppFn.isConst do return none
|
||||
let declName := f.getAppFn.constName!
|
||||
let env ← getEnv
|
||||
for namedArg in namedArgs do
|
||||
if let some entry := findDeprecatedArg? env declName namedArg.name then
|
||||
if entry.newArg? == some binderName then
|
||||
let msg := formatDeprecatedArgMsg entry
|
||||
let span? := namedArg.ref[1]
|
||||
let hint ←
|
||||
if span?.getHeadInfo matches .original .. then
|
||||
MessageData.hint "Rename this argument:" #[{
|
||||
suggestion := .string entry.newArg?.get!.toString
|
||||
span?
|
||||
toCodeActionTitle? := some fun s =>
|
||||
s!"Rename argument `{entry.oldArg}` to `{s}`"
|
||||
}]
|
||||
else
|
||||
pure .nil
|
||||
logWarningAt namedArg.ref <| .tagged ``deprecatedArgExt msg ++ hint
|
||||
return some namedArg
|
||||
return none
|
||||
|
||||
/-- Erase entry for `binderName` from `namedArgs`. -/
|
||||
def eraseNamedArg (namedArgs : List NamedArg) (binderName : Name) : List NamedArg :=
|
||||
namedArgs.filter (·.name != binderName)
|
||||
@@ -238,6 +271,23 @@ private def synthesizePendingAndNormalizeFunType : M Unit := do
|
||||
else
|
||||
for namedArg in s.namedArgs do
|
||||
let f := s.f.getAppFn
|
||||
if f.isConst then
|
||||
let env ← getEnv
|
||||
if linter.deprecated.arg.get (← getOptions) then
|
||||
if let some entry := findDeprecatedArg? env f.constName! namedArg.name then
|
||||
if entry.newArg?.isNone then
|
||||
let msg := formatDeprecatedArgMsg entry
|
||||
let hint ←
|
||||
if namedArg.ref.getHeadInfo matches .original .. then
|
||||
MessageData.hint "Delete this argument:" #[{
|
||||
suggestion := .string ""
|
||||
span? := namedArg.ref
|
||||
toCodeActionTitle? := some fun _ =>
|
||||
s!"Delete deprecated argument `{entry.oldArg}`"
|
||||
}]
|
||||
else
|
||||
pure .nil
|
||||
throwErrorAt namedArg.ref (msg ++ hint)
|
||||
let validNames ← getFoundNamedArgs
|
||||
let fnName? := if f.isConst then some f.constName! else none
|
||||
throwInvalidNamedArg namedArg fnName? validNames
|
||||
@@ -756,13 +806,16 @@ mutual
|
||||
let binderName := fType.bindingName!
|
||||
let binfo := fType.bindingInfo!
|
||||
let s ← get
|
||||
match findBinderName? s.namedArgs binderName with
|
||||
let namedArg? ← match findBinderName? s.namedArgs binderName with
|
||||
| some namedArg => pure (some namedArg)
|
||||
| none => findDeprecatedBinderName? s.namedArgs s.f binderName
|
||||
match namedArg? with
|
||||
| some namedArg =>
|
||||
propagateExpectedType namedArg.val
|
||||
eraseNamedArg binderName
|
||||
eraseNamedArg namedArg.name
|
||||
elabAndAddNewArg binderName namedArg.val
|
||||
main
|
||||
| none =>
|
||||
| none =>
|
||||
unless binderName.hasMacroScopes do
|
||||
pushFoundNamedArg binderName
|
||||
match binfo with
|
||||
@@ -1779,13 +1832,15 @@ To infer a namespace from the expected type, we do the following operations:
|
||||
- if the type is of the form `c x₁ ... xₙ` with `c` a constant, then try using `c` as the namespace,
|
||||
and if that doesn't work, try unfolding the expression and continuing.
|
||||
-/
|
||||
private partial def resolveDottedIdentFn (idRef : Syntax) (id : Name) (expectedType? : Option Expr) : TermElabM (List (Expr × Syntax × List Syntax)) := do
|
||||
private partial def resolveDottedIdentFn (idRef : Syntax) (id : Name) (explicitUnivs : List Level) (expectedType? : Option Expr) : TermElabM (List (Expr × Syntax × List Syntax)) := do
|
||||
unless id.isAtomic do
|
||||
throwError "Invalid dotted identifier notation: The name `{id}` must be atomic"
|
||||
tryPostponeIfNoneOrMVar expectedType?
|
||||
let some expectedType := expectedType?
|
||||
| throwNoExpectedType
|
||||
addCompletionInfo <| CompletionInfo.dotId idRef id (← getLCtx) expectedType?
|
||||
-- We will check deprecations in `elabAppFnResolutions`.
|
||||
withoutCheckDeprecated do
|
||||
withForallBody expectedType fun resultType => do
|
||||
go resultType expectedType #[]
|
||||
where
|
||||
@@ -1825,8 +1880,10 @@ where
|
||||
|>.filter (fun (_, fieldList) => fieldList.isEmpty)
|
||||
|>.map Prod.fst
|
||||
if !candidates.isEmpty then
|
||||
candidates.mapM fun resolvedName => return (← mkConst resolvedName, ← getRef, [])
|
||||
candidates.mapM fun resolvedName => return (← mkConst resolvedName explicitUnivs, ← getRef, [])
|
||||
else if let some (fvar, []) ← resolveLocalName fullName then
|
||||
unless explicitUnivs.isEmpty do
|
||||
throwInvalidExplicitUniversesForLocal fvar
|
||||
return [(fvar, ← getRef, [])]
|
||||
else
|
||||
throwUnknownIdentifierAt (← getRef) (declHint := fullName) <| m!"Unknown constant `{.ofConstName fullName}`"
|
||||
@@ -1866,6 +1923,10 @@ private partial def elabAppFn (f : Syntax) (lvals : List LVal) (namedArgs : Arra
|
||||
let some idx := idxStx.isFieldIdx?
|
||||
| throwError "Internal error: Unexpected field index syntax `{idxStx}`"
|
||||
elabAppFn e (LVal.fieldIdx idxStx idx :: lvals) namedArgs args expectedType? explicit ellipsis overloaded acc
|
||||
let elabDottedIdent (id : Syntax) (explicitUnivs : List Level) (explicit : Bool) : TermElabM (Array (TermElabResult Expr)) := do
|
||||
let res ← withRef f <| resolveDottedIdentFn id id.getId.eraseMacroScopes explicitUnivs expectedType?
|
||||
-- Use (forceTermInfo := true) because we want to record the result of .ident resolution even in patterns
|
||||
elabAppFnResolutions f res lvals namedArgs args expectedType? explicit ellipsis overloaded acc (forceTermInfo := true)
|
||||
match f with
|
||||
| `($(e).$idx:fieldIdx) => elabFieldIdx e idx explicit
|
||||
| `($e |>.$idx:fieldIdx) => elabFieldIdx e idx explicit
|
||||
@@ -1881,16 +1942,17 @@ private partial def elabAppFn (f : Syntax) (lvals : List LVal) (namedArgs : Arra
|
||||
| `($id:ident.{$us,*}) => do
|
||||
let us ← elabExplicitUnivs us
|
||||
elabAppFnId id us lvals namedArgs args expectedType? explicit ellipsis overloaded acc
|
||||
| `(@$id:ident) =>
|
||||
elabAppFn id lvals namedArgs args expectedType? (explicit := true) ellipsis overloaded acc
|
||||
| `(@$_:ident.{$_us,*}) =>
|
||||
| `(.$id:ident) => elabDottedIdent id [] explicit
|
||||
| `(.$id:ident.{$us,*}) =>
|
||||
let us ← elabExplicitUnivs us
|
||||
elabDottedIdent id us explicit
|
||||
| `(@$_:ident)
|
||||
| `(@$_:ident.{$_us,*})
|
||||
| `(@.$_:ident)
|
||||
| `(@.$_:ident.{$_us,*}) =>
|
||||
elabAppFn (f.getArg 1) lvals namedArgs args expectedType? (explicit := true) ellipsis overloaded acc
|
||||
| `(@$_) => throwUnsupportedSyntax -- invalid occurrence of `@`
|
||||
| `(_) => throwError "A placeholder `_` cannot be used where a function is expected"
|
||||
| `(.$id:ident) =>
|
||||
let res ← withRef f <| resolveDottedIdentFn id id.getId.eraseMacroScopes expectedType?
|
||||
-- Use (forceTermInfo := true) because we want to record the result of .ident resolution even in patterns
|
||||
elabAppFnResolutions f res lvals namedArgs args expectedType? explicit ellipsis overloaded acc (forceTermInfo := true)
|
||||
| _ => do
|
||||
let catchPostpone := !overloaded
|
||||
/- If we are processing a choice node, then we should use `catchPostpone == false` when elaborating terms.
|
||||
@@ -2033,13 +2095,15 @@ private def elabAtom : TermElab := fun stx expectedType? => do
|
||||
|
||||
@[builtin_term_elab explicit] def elabExplicit : TermElab := fun stx expectedType? =>
|
||||
match stx with
|
||||
| `(@$_:ident) => elabAtom stx expectedType? -- Recall that `elabApp` also has support for `@`
|
||||
| `(@$_:ident.{$_us,*}) => elabAtom stx expectedType?
|
||||
| `(@$(_).$_:fieldIdx) => elabAtom stx expectedType?
|
||||
| `(@$(_).$_:ident) => elabAtom stx expectedType?
|
||||
| `(@($t)) => elabTerm t expectedType? (implicitLambda := false) -- `@` is being used just to disable implicit lambdas
|
||||
| `(@$t) => elabTerm t expectedType? (implicitLambda := false) -- `@` is being used just to disable implicit lambdas
|
||||
| _ => throwUnsupportedSyntax
|
||||
| `(@$_:ident) => elabAtom stx expectedType? -- Recall that `elabApp` also has support for `@`
|
||||
| `(@$_:ident.{$_us,*}) => elabAtom stx expectedType?
|
||||
| `(@$(_).$_:fieldIdx) => elabAtom stx expectedType?
|
||||
| `(@$(_).$_:ident) => elabAtom stx expectedType?
|
||||
| `(@.$_:ident) => elabAtom stx expectedType?
|
||||
| `(@.$_:ident.{$_us,*}) => elabAtom stx expectedType?
|
||||
| `(@($t)) => elabTerm t expectedType? (implicitLambda := false) -- `@` is being used just to disable implicit lambdas
|
||||
| `(@$t) => elabTerm t expectedType? (implicitLambda := false) -- `@` is being used just to disable implicit lambdas
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
@[builtin_term_elab choice] def elabChoice : TermElab := elabAtom
|
||||
@[builtin_term_elab proj] def elabProj : TermElab := elabAtom
|
||||
|
||||
@@ -9,10 +9,12 @@ prelude
|
||||
public import Lean.Meta.Reduce
|
||||
public import Lean.Elab.Eval
|
||||
public import Lean.Elab.Command
|
||||
import Lean.Elab.DeprecatedSyntax
|
||||
public import Lean.Elab.Open
|
||||
import Init.Data.Nat.Order
|
||||
import Init.Data.Order.Lemmas
|
||||
import Init.System.Platform
|
||||
import Lean.DeprecatedModule
|
||||
|
||||
public section
|
||||
|
||||
@@ -512,6 +514,15 @@ def failIfSucceeds (x : CommandElabM Unit) : CommandElabM Unit := do
|
||||
modify fun s => { s with maxRecDepth := maxRecDepth.get options }
|
||||
modifyScope fun scope => { scope with opts := options }
|
||||
|
||||
@[builtin_command_elab «unlock_limits»] def elabUnlockLimits : CommandElab := fun _ => do
|
||||
let opts ← getOptions
|
||||
let opts := maxHeartbeats.set opts 0
|
||||
let opts := maxRecDepth.set opts 0
|
||||
let opts := synthInstance.maxHeartbeats.set opts 0
|
||||
modifyScope ({ · with opts })
|
||||
-- update cached value as well
|
||||
modify ({ · with maxRecDepth := 0 })
|
||||
|
||||
open Lean.Parser.Command.InternalSyntax in
|
||||
@[builtin_macro Lean.Parser.Command.«in»] def expandInCmd : Macro
|
||||
| `($cmd₁ in%$tk $cmd₂) =>
|
||||
@@ -706,4 +717,54 @@ where
|
||||
let env ← getEnv
|
||||
IO.eprintln (← env.dbgFormatAsyncState)
|
||||
|
||||
/-- Elaborate `deprecated_module`, marking the current module as deprecated. -/
|
||||
@[builtin_command_elab Parser.Command.deprecated_module]
|
||||
def elabDeprecatedModule : CommandElab
|
||||
| `(Parser.Command.deprecated_module| deprecated_module $[$msg?]? $[(since := $since?)]?) => do
|
||||
let message? := msg?.map TSyntax.getString
|
||||
let since? := since?.map TSyntax.getString
|
||||
if (deprecatedModuleExt.getState (← getEnv)).isSome then
|
||||
logWarning "module is already marked as deprecated"
|
||||
if since?.isNone then
|
||||
logWarning "`deprecated_module` should specify the date or library version \
|
||||
at which the deprecation was introduced, using `(since := \"...\")`"
|
||||
modifyEnv fun env => env.setDeprecatedModule (some { message?, since? })
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
/-- Elaborate `#show_deprecated_modules`, displaying all deprecated modules. -/
|
||||
@[builtin_command_elab Parser.Command.showDeprecatedModules]
|
||||
def elabShowDeprecatedModules : CommandElab := fun _ => do
|
||||
let env ← getEnv
|
||||
let mut parts : Array String := #["Deprecated modules\n"]
|
||||
for h : idx in [:env.header.moduleNames.size] do
|
||||
if let some entry := env.getDeprecatedModuleByIdx? idx then
|
||||
let modName := env.header.moduleNames[idx]
|
||||
let msg := match entry.message? with
|
||||
| some str => s!"message '{str}'"
|
||||
| none => "no message"
|
||||
let replacements := env.header.moduleData[idx]!.imports.filter fun imp =>
|
||||
imp.module != `Init
|
||||
parts := parts.push s!"'{modName}' deprecates to\n{replacements.map (·.module)}\nwith {msg}\n"
|
||||
-- Also show the current module's deprecation if set.
|
||||
if let some entry := deprecatedModuleExt.getState env then
|
||||
let modName := env.mainModule
|
||||
let msg := match entry.message? with
|
||||
| some str => s!"message '{str}'"
|
||||
| none => "no message"
|
||||
let replacements := env.imports.filter fun imp =>
|
||||
imp.module != `Init
|
||||
parts := parts.push s!"'{modName}' deprecates to\n{replacements.map (·.module)}\nwith {msg}\n"
|
||||
logInfo (String.intercalate "\n" parts.toList)
|
||||
|
||||
@[builtin_command_elab Parser.Command.deprecatedSyntax] def elabDeprecatedSyntax : CommandElab := fun stx => do
|
||||
let id := stx[1]
|
||||
let kind ← liftCoreM <| checkSyntaxNodeKindAtNamespaces id.getId (← getCurrNamespace)
|
||||
let text? := if stx[2].isNone then none else stx[2][0].isStrLit?
|
||||
let since? := if stx[3].isNone then none else stx[3][3].isStrLit?
|
||||
if since?.isNone then
|
||||
logWarning "`deprecated_syntax` should specify the date or library version at which the \
|
||||
deprecation was introduced, using `(since := \"...\")`"
|
||||
modifyEnv fun env =>
|
||||
deprecatedSyntaxExt.addEntry env { kind, text?, since? }
|
||||
|
||||
end Lean.Elab.Command
|
||||
|
||||
@@ -63,6 +63,6 @@ where
|
||||
doElabToSyntax "else branch of if with condition {cond}" (elabDiteBranch false) fun else_ => do
|
||||
let mγ ← mkMonadicType (← read).doBlockResultType
|
||||
match h with
|
||||
| `(_%$tk) => Term.elabTermEnsuringType (← `(if $(⟨tk⟩):hole : $cond then $then_ else $else_)) mγ
|
||||
| `(_%$tk) => Term.elabTermEnsuringType (← `(if _%$tk : $cond then $then_ else $else_)) mγ
|
||||
| `($h:ident) => Term.elabTermEnsuringType (← `(if $h:ident : $cond then $then_ else $else_)) mγ
|
||||
| _ => throwUnsupportedSyntax
|
||||
|
||||
@@ -43,7 +43,7 @@ builtin_initialize
|
||||
|
||||
Upon such rewrite, the code for adding flat inductives does not diverge much from the usual
|
||||
way its done for inductive declarations, but we omit applying attributes/modifiers and
|
||||
we do not set the syntax references to track those declarations (as this is auxillary piece of
|
||||
we do not set the syntax references to track those declarations (as this is auxiliary piece of
|
||||
data hidden from the user).
|
||||
|
||||
Then, upon adding such flat inductives for each definition in the mutual block to the environment,
|
||||
@@ -345,7 +345,7 @@ private def mkCasesOnCoinductive (infos : Array InductiveVal) : MetaM Unit := do
|
||||
| throwError "expected to be quantifier"
|
||||
let motiveMVar ← mkFreshExprMVar type
|
||||
/-
|
||||
We intro all the indices and the occurence of the coinductive predicate
|
||||
We intro all the indices and the occurrence of the coinductive predicate
|
||||
-/
|
||||
let (fvars, subgoal) ← motiveMVar.mvarId!.introN (info.numIndices + 1)
|
||||
subgoal.withContext do
|
||||
@@ -373,7 +373,7 @@ private def mkCasesOnCoinductive (infos : Array InductiveVal) : MetaM Unit := do
|
||||
-/
|
||||
let originalCasesOn := mkAppN originalCasesOn indices
|
||||
/-
|
||||
The next argument is the occurence of the coinductive predicate.
|
||||
The next argument is the occurrence of the coinductive predicate.
|
||||
The original `casesOn` of the flat inductive mentions it in
|
||||
unrolled form, so we need to rewrite it.
|
||||
-/
|
||||
@@ -447,7 +447,7 @@ public def elabCoinductive (coinductiveElabData : Array CoinductiveElabData) : T
|
||||
let consts := namesAndTypes.map fun (name, _) => (mkConst name levelParams)
|
||||
/-
|
||||
We create values of each of PreDefinitions, by taking existential (see `Meta.SumOfProducts`)
|
||||
form of the associated flat inductives and applying paramaters, as well as recursive calls
|
||||
form of the associated flat inductives and applying parameters, as well as recursive calls
|
||||
(with their parameters passed).
|
||||
-/
|
||||
let preDefVals ← forallBoundedTelescope infos[0]!.type originalNumParams fun params _ => do
|
||||
|
||||
@@ -10,6 +10,7 @@ public import Lean.Meta.Diagnostics
|
||||
public import Lean.Elab.Binders
|
||||
public import Lean.Elab.Command.Scope
|
||||
public import Lean.Elab.SetOption
|
||||
import Lean.Elab.DeprecatedSyntax
|
||||
public meta import Lean.Parser.Command
|
||||
|
||||
public section
|
||||
@@ -468,6 +469,7 @@ where go := do
|
||||
else withTraceNode `Elab.command (fun _ => return stx) (tag :=
|
||||
-- special case: show actual declaration kind for `declaration` commands
|
||||
(if stx.isOfKind ``Parser.Command.declaration then stx[1] else stx).getKind.toString) do
|
||||
checkDeprecatedSyntax stx (← read).macroStack
|
||||
let s ← get
|
||||
match (← liftMacroM <| expandMacroImpl? s.env stx) with
|
||||
| some (decl, stxNew?) =>
|
||||
|
||||
97
src/Lean/Elab/DeprecatedArg.lean
Normal file
97
src/Lean/Elab/DeprecatedArg.lean
Normal file
@@ -0,0 +1,97 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Wojciech Różowski
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Lean.EnvExtension
|
||||
public import Lean.Message
|
||||
import Lean.Elab.Term
|
||||
|
||||
public section
|
||||
|
||||
namespace Lean.Elab
|
||||
open Meta
|
||||
|
||||
register_builtin_option linter.deprecated.arg : Bool := {
|
||||
defValue := true
|
||||
descr := "if true, generate deprecation warnings and errors for deprecated parameters"
|
||||
}
|
||||
|
||||
/-- Entry mapping an old parameter name to a new (or no) parameter for a given declaration. -/
|
||||
structure DeprecatedArgEntry where
|
||||
declName : Name
|
||||
oldArg : Name
|
||||
newArg? : Option Name := none
|
||||
text? : Option String := none
|
||||
since? : Option String := none
|
||||
deriving Inhabited
|
||||
|
||||
/-- State: `declName → (oldArg → entry)` -/
|
||||
abbrev DeprecatedArgState := NameMap (NameMap DeprecatedArgEntry)
|
||||
|
||||
private def addDeprecatedArgEntry (s : DeprecatedArgState) (e : DeprecatedArgEntry) : DeprecatedArgState :=
|
||||
let inner := (s.find? e.declName).getD {} |>.insert e.oldArg e
|
||||
s.insert e.declName inner
|
||||
|
||||
builtin_initialize deprecatedArgExt :
|
||||
SimplePersistentEnvExtension DeprecatedArgEntry DeprecatedArgState ←
|
||||
registerSimplePersistentEnvExtension {
|
||||
addEntryFn := addDeprecatedArgEntry
|
||||
addImportedFn := mkStateFromImportedEntries addDeprecatedArgEntry {}
|
||||
}
|
||||
|
||||
/-- Look up a deprecated argument mapping for `(declName, argName)`. -/
|
||||
def findDeprecatedArg? (env : Environment) (declName : Name) (argName : Name) :
|
||||
Option DeprecatedArgEntry :=
|
||||
(deprecatedArgExt.getState env |>.find? declName) >>= (·.find? argName)
|
||||
|
||||
/-- Format the deprecation warning message for a deprecated argument. -/
|
||||
def formatDeprecatedArgMsg (entry : DeprecatedArgEntry) : MessageData :=
|
||||
let base := match entry.newArg? with
|
||||
| some newArg =>
|
||||
m!"parameter `{entry.oldArg}` of `{.ofConstName entry.declName}` has been deprecated, \
|
||||
use `{newArg}` instead"
|
||||
| none =>
|
||||
m!"parameter `{entry.oldArg}` of `{.ofConstName entry.declName}` has been deprecated"
|
||||
match entry.text? with
|
||||
| some text => base ++ m!": {text}"
|
||||
| none => base
|
||||
|
||||
builtin_initialize registerBuiltinAttribute {
|
||||
name := `deprecated_arg
|
||||
descr := "mark a parameter as deprecated"
|
||||
add := fun declName stx _kind => do
|
||||
let `(attr| deprecated_arg $oldId $[$newId?]? $[$text?]? $[(since := $since?)]?) := stx
|
||||
| throwError "Invalid `[deprecated_arg]` attribute syntax"
|
||||
let oldArg := oldId.getId
|
||||
let newArg? := newId?.map TSyntax.getId
|
||||
let text? := text?.map TSyntax.getString |>.filter (!·.isEmpty)
|
||||
let since? := since?.map TSyntax.getString
|
||||
let info ← getConstInfo declName
|
||||
let paramNames ← MetaM.run' do
|
||||
forallTelescopeReducing info.type fun xs _ =>
|
||||
xs.mapM fun x => return (← x.fvarId!.getDecl).userName
|
||||
if let some newArg := newArg? then
|
||||
-- We have a replacement provided
|
||||
unless Array.any paramNames (· == newArg) do
|
||||
throwError "`{newArg}` is not a parameter of `{declName}`"
|
||||
if Array.any paramNames (· == oldArg) then
|
||||
throwError "`{oldArg}` is still a parameter of `{declName}`; \
|
||||
rename it to `{newArg}` before adding `@[deprecated_arg]`"
|
||||
else
|
||||
-- We do not have a replacement provided
|
||||
if Array.any paramNames (· == oldArg) then
|
||||
throwError "`{oldArg}` is still a parameter of `{declName}`; \
|
||||
remove it before adding `@[deprecated_arg]`"
|
||||
if since?.isNone then
|
||||
logWarning "`[deprecated_arg]` attribute should specify the date or library version \
|
||||
at which the deprecation was introduced, using `(since := \"...\")`"
|
||||
modifyEnv fun env => deprecatedArgExt.addEntry env {
|
||||
declName, oldArg, newArg?, text?, since?
|
||||
}
|
||||
}
|
||||
|
||||
end Lean.Elab
|
||||
71
src/Lean/Elab/DeprecatedSyntax.lean
Normal file
71
src/Lean/Elab/DeprecatedSyntax.lean
Normal file
@@ -0,0 +1,71 @@
|
||||
/-
|
||||
Copyright (c) 2026 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Wojciech Różowski
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Lean.MonadEnv
|
||||
public import Lean.Linter.Basic
|
||||
public import Lean.Elab.Util
|
||||
|
||||
public section
|
||||
|
||||
namespace Lean.Linter
|
||||
|
||||
register_builtin_option linter.deprecated.syntax : Bool := {
|
||||
defValue := true
|
||||
descr := "if true, generate warnings when deprecated syntax is used"
|
||||
}
|
||||
|
||||
end Lean.Linter
|
||||
|
||||
namespace Lean.Elab
|
||||
|
||||
/-- Entry recording that a syntax kind has been deprecated. -/
|
||||
structure SyntaxDeprecationEntry where
|
||||
/-- The syntax node kind that is deprecated. -/
|
||||
kind : SyntaxNodeKind
|
||||
/-- Optional deprecation message. -/
|
||||
text? : Option String := none
|
||||
/-- Optional version or date at which the syntax was deprecated. -/
|
||||
since? : Option String := none
|
||||
|
||||
builtin_initialize deprecatedSyntaxExt :
|
||||
SimplePersistentEnvExtension SyntaxDeprecationEntry (NameMap SyntaxDeprecationEntry) ←
|
||||
registerSimplePersistentEnvExtension {
|
||||
addImportedFn := mkStateFromImportedEntries (fun m e => m.insert e.kind e) {}
|
||||
addEntryFn := fun m e => m.insert e.kind e
|
||||
}
|
||||
|
||||
/--
|
||||
Check whether `stx` is a deprecated syntax kind, and if so, emit a warning.
|
||||
|
||||
If `macroStack` is non-empty, the warning is attributed to the macro call site rather than the
|
||||
syntax itself.
|
||||
-/
|
||||
def checkDeprecatedSyntax [Monad m] [MonadEnv m] [MonadLog m] [MonadOptions m]
|
||||
[AddMessageContext m] [MonadRef m] (stx : Syntax) (macroStack : MacroStack) : m Unit := do
|
||||
let env ← getEnv
|
||||
let kind := stx.getKind
|
||||
if let some entry := (deprecatedSyntaxExt.getState env).find? kind then
|
||||
let extraMsg := match entry.text? with
|
||||
| some text => m!": {text}"
|
||||
| none => m!""
|
||||
match macroStack with
|
||||
| { before := macroStx, .. } :: { before := callerStx, .. } :: _ =>
|
||||
let expandedFrom :=
|
||||
if callerStx.getKind != macroStx.getKind then
|
||||
m!" (expanded from '{callerStx.getKind}')"
|
||||
else m!""
|
||||
Linter.logLintIf Linter.linter.deprecated.syntax macroStx
|
||||
m!"macro '{macroStx.getKind}'{expandedFrom} produces deprecated syntax '{kind}'{extraMsg}"
|
||||
| { before := macroStx, .. } :: [] =>
|
||||
Linter.logLintIf Linter.linter.deprecated.syntax macroStx
|
||||
m!"macro '{macroStx.getKind}' produces deprecated syntax '{kind}'{extraMsg}"
|
||||
| [] =>
|
||||
Linter.logLintIf Linter.linter.deprecated.syntax stx
|
||||
m!"syntax '{kind}' has been deprecated{extraMsg}"
|
||||
|
||||
end Lean.Elab
|
||||
@@ -85,6 +85,10 @@ structure State where
|
||||
-/
|
||||
lctx : LocalContext
|
||||
/--
|
||||
The local instances.
|
||||
|
||||
The `MonadLift TermElabM DocM` instance runs the lifted action with these instances, so elaboration
|
||||
commands that mutate this state cause it to take effect in subsequent commands.
|
||||
-/
|
||||
localInstances : LocalInstances
|
||||
/--
|
||||
|
||||
@@ -9,6 +9,7 @@ prelude
|
||||
public import Lean.Parser.Module
|
||||
meta import Lean.Parser.Module
|
||||
import Lean.Compiler.ModPkgExt
|
||||
public import Lean.DeprecatedModule
|
||||
|
||||
public section
|
||||
|
||||
@@ -42,12 +43,66 @@ def HeaderSyntax.toModuleHeader (stx : HeaderSyntax) : ModuleHeader where
|
||||
|
||||
abbrev headerToImports := @HeaderSyntax.imports
|
||||
|
||||
/--
|
||||
Check imported modules for deprecation and emit warnings.
|
||||
|
||||
The `-- deprecated_module: ignore` comment can be placed on the `module` keyword to suppress
|
||||
all warnings, or on individual `import` statements to suppress specific ones.
|
||||
This follows the same pattern as `-- shake: keep` in Lake shake.
|
||||
|
||||
The `headerStx?` parameter carries the header syntax used for checking trailing comments.
|
||||
When called from the Language Server, the main header syntax may have its trailing trivia
|
||||
stripped by `unsetTrailing` for caching purposes, so `origHeaderStx?` can supply the original
|
||||
(untrimmed) syntax to preserve `-- deprecated_module: ignore` annotations on the last import.
|
||||
-/
|
||||
def checkDeprecatedImports
|
||||
(env : Environment) (imports : Array Import) (opts : Options)
|
||||
(inputCtx : Parser.InputContext) (startPos : String.Pos.Raw) (messages : MessageLog)
|
||||
(headerStx? : Option HeaderSyntax := none)
|
||||
(origHeaderStx? : Option HeaderSyntax := none)
|
||||
: MessageLog := Id.run do
|
||||
let mut opts := opts
|
||||
let mut ignoreDeprecatedImports : NameSet := {}
|
||||
if let some headerStx := origHeaderStx? <|> headerStx? then
|
||||
match headerStx with
|
||||
| `(Parser.Module.header| $[module%$moduleTk]? $[prelude%$_]? $importsStx*) =>
|
||||
if moduleTk.any (·.getTrailing?.any (·.toString.contains "deprecated_module: ignore")) then
|
||||
opts := linter.deprecated.module.set opts false
|
||||
for impStx in importsStx do
|
||||
if impStx.raw.getTrailing?.any (·.toString.contains "deprecated_module: ignore") then
|
||||
match impStx with
|
||||
| `(Parser.Module.import| $[public%$_]? $[meta%$_]? import $[all%$_]? $n) =>
|
||||
ignoreDeprecatedImports := ignoreDeprecatedImports.insert n.getId
|
||||
| _ => pure ()
|
||||
| _ => pure ()
|
||||
if !linter.deprecated.module.get opts then
|
||||
return messages
|
||||
imports.foldl (init := messages) fun messages imp =>
|
||||
if ignoreDeprecatedImports.contains imp.module then
|
||||
messages
|
||||
else
|
||||
match env.getModuleIdx? imp.module with
|
||||
| some idx =>
|
||||
match env.getDeprecatedModuleByIdx? idx with
|
||||
| some entry =>
|
||||
let pos := inputCtx.fileMap.toPosition startPos
|
||||
messages.add {
|
||||
fileName := inputCtx.fileName
|
||||
pos := pos
|
||||
severity := .warning
|
||||
data := .tagged ``deprecatedModuleExt <| formatDeprecatedModuleWarning env idx imp.module entry
|
||||
}
|
||||
| none => messages
|
||||
| none => messages
|
||||
|
||||
def processHeaderCore
|
||||
(startPos : String.Pos.Raw) (imports : Array Import) (isModule : Bool)
|
||||
(opts : Options) (messages : MessageLog) (inputCtx : Parser.InputContext)
|
||||
(trustLevel : UInt32 := 0) (plugins : Array System.FilePath := #[]) (leakEnv := false)
|
||||
(mainModule := Name.anonymous) (package? : Option PkgId := none)
|
||||
(arts : NameMap ImportArtifacts := {})
|
||||
(headerStx? : Option HeaderSyntax := none)
|
||||
(origHeaderStx? : Option HeaderSyntax := none)
|
||||
: IO (Environment × MessageLog) := do
|
||||
let level := if isModule then
|
||||
if Elab.inServer.get opts then
|
||||
@@ -66,6 +121,7 @@ def processHeaderCore
|
||||
let pos := inputCtx.fileMap.toPosition startPos
|
||||
pure (env, messages.add { fileName := inputCtx.fileName, data := toString e, pos := pos })
|
||||
let env := env.setMainModule mainModule |>.setModulePackage package?
|
||||
let messages := checkDeprecatedImports env imports opts inputCtx startPos messages headerStx? origHeaderStx?
|
||||
return (env, messages)
|
||||
|
||||
/--
|
||||
@@ -82,6 +138,7 @@ backwards compatibility measure not compatible with the module system.
|
||||
: IO (Environment × MessageLog) := do
|
||||
processHeaderCore header.startPos header.imports header.isModule
|
||||
opts messages inputCtx trustLevel plugins leakEnv mainModule
|
||||
(headerStx? := header)
|
||||
|
||||
def parseImports (input : String) (fileName : Option String := none) : IO (Array Import × Position × MessageLog) := do
|
||||
let fileName := fileName.getD "<input>"
|
||||
|
||||
@@ -73,8 +73,9 @@ def splitMatchOrCasesOn (mvarId : MVarId) (e : Expr) (matcherInfo : MatcherInfo)
|
||||
if (← isMatcherApp e) then
|
||||
Split.splitMatch mvarId e
|
||||
else
|
||||
assert! matcherInfo.numDiscrs = 1
|
||||
let discr := e.getAppArgs[matcherInfo.numParams + 1]!
|
||||
-- For casesOn, the last discriminant is the major premise;
|
||||
-- `cases` will handle any index discriminants automatically.
|
||||
let discr := e.getAppArgs[matcherInfo.numParams + matcherInfo.numDiscrs]!
|
||||
assert! discr.isFVar
|
||||
let subgoals ← mvarId.cases discr.fvarId!
|
||||
return subgoals.map (·.mvarId) |>.toList
|
||||
|
||||
@@ -7,6 +7,7 @@ module
|
||||
|
||||
prelude
|
||||
public import Lean.Elab.Quotation.Util
|
||||
import Lean.Elab.DeprecatedSyntax
|
||||
|
||||
public section
|
||||
|
||||
@@ -56,6 +57,14 @@ unsafe builtin_initialize precheckAttribute : KeyedDeclsAttribute Precheck ←
|
||||
}
|
||||
|
||||
partial def precheck : Precheck := fun stx => do
|
||||
-- Check for deprecated syntax kinds in quotations
|
||||
if let some entry := (deprecatedSyntaxExt.getState (← getEnv)).find? stx.getKind then
|
||||
let extraMsg := match entry.text? with
|
||||
| some text => m!": {text}"
|
||||
| none => m!""
|
||||
withRef stx do
|
||||
Linter.logLintIf Linter.linter.deprecated.syntax stx
|
||||
m!"quotation uses deprecated syntax '{stx.getKind}'{extraMsg}"
|
||||
if let p::_ := precheckAttribute.getValues (← getEnv) stx.getKind then
|
||||
if ← catchInternalId unsupportedSyntaxExceptionId (do withRef stx <| p stx; pure true) (fun _ => pure false) then
|
||||
return
|
||||
|
||||
@@ -582,6 +582,7 @@ mutual
|
||||
-- We use `filterRevM` instead of `filterM` to make sure we process the synthetic metavariables using the order they were created.
|
||||
-- It would not be incorrect to use `filterM`.
|
||||
let remainingPendingMVars ← pendingMVars.filterRevM fun mvarId => do
|
||||
checkSystem "synthesize pending MVars"
|
||||
-- We use `traceM` because we want to make sure the metavar local context is used to trace the message
|
||||
traceM `Elab.postpone (mvarId.withContext do addMessageContext m!"resuming {mkMVar mvarId}")
|
||||
let succeeded ← synthesizeSyntheticMVar mvarId postponeOnError runTactics
|
||||
|
||||
@@ -8,6 +8,7 @@ module
|
||||
prelude
|
||||
public import Lean.Meta.Tactic.Util
|
||||
public import Lean.Elab.Term
|
||||
import Lean.Elab.DeprecatedSyntax
|
||||
import Init.Omega
|
||||
|
||||
public section
|
||||
@@ -192,6 +193,7 @@ partial def evalTactic (stx : Syntax) : TacticM Unit := do
|
||||
Term.withoutTacticIncrementality true <| withTacticInfoContext stx do
|
||||
stx.getArgs.forM evalTactic
|
||||
else withTraceNode `Elab.step (fun _ => return stx) (tag := stx.getKind.toString) do
|
||||
checkDeprecatedSyntax stx (← readThe Term.Context).macroStack
|
||||
let evalFns := tacticElabAttribute.getEntries (← getEnv) stx.getKind
|
||||
let macros := macroAttribute.getEntries (← getEnv) stx.getKind
|
||||
if evalFns.isEmpty && macros.isEmpty then
|
||||
|
||||
@@ -7,6 +7,8 @@ module
|
||||
prelude
|
||||
public import Lean.Elab.Tactic.Grind.Basic
|
||||
import Lean.Elab.Tactic.ConfigSetter
|
||||
import Lean.Elab.DeprecatedSyntax -- shake: skip (workaround for `mkConfigSetter` failing to interpret `deprecatedSyntaxExt`, to be fixed)
|
||||
|
||||
public section
|
||||
namespace Lean.Elab.Tactic.Grind
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ prelude
|
||||
public import Lean.Meta.Coe
|
||||
public import Lean.Util.CollectLevelMVars
|
||||
public import Lean.Linter.Deprecated
|
||||
import Lean.Elab.DeprecatedSyntax
|
||||
public import Lean.Elab.Attributes
|
||||
public import Lean.Elab.Level
|
||||
public import Lean.Elab.PreDefinition.TerminationHint
|
||||
@@ -1794,6 +1795,7 @@ private partial def elabTermAux (expectedType? : Option Expr) (catchExPostpone :
|
||||
withTraceNode `Elab.step (fun _ => return m!"expected type: {expectedType?}, term\n{stx}")
|
||||
(tag := stx.getKind.toString) do
|
||||
checkSystem "elaborator"
|
||||
checkDeprecatedSyntax stx (← read).macroStack
|
||||
let env ← getEnv
|
||||
let result ← match (← liftMacroM (expandMacroImpl? env stx)) with
|
||||
| some (decl, stxNew?) =>
|
||||
@@ -2122,11 +2124,14 @@ private def mkConsts (candidates : List (Name × List String)) (explicitLevels :
|
||||
let const ← withoutCheckDeprecated <| mkConst declName explicitLevels
|
||||
return (const, projs) :: result
|
||||
|
||||
def throwInvalidExplicitUniversesForLocal {α} (e : Expr) : TermElabM α :=
|
||||
throwError "invalid use of explicit universe parameters, `{e}` is a local variable"
|
||||
|
||||
def resolveName (stx : Syntax) (n : Name) (preresolved : List Syntax.Preresolved) (explicitLevels : List Level) (expectedType? : Option Expr := none) : TermElabM (List (Expr × List String)) := do
|
||||
addCompletionInfo <| CompletionInfo.id stx stx.getId (danglingDot := false) (← getLCtx) expectedType?
|
||||
if let some (e, projs) ← resolveLocalName n then
|
||||
unless explicitLevels.isEmpty do
|
||||
throwError "invalid use of explicit universe parameters, `{e}` is a local variable"
|
||||
throwInvalidExplicitUniversesForLocal e
|
||||
return [(e, projs)]
|
||||
let preresolved := preresolved.filterMap fun
|
||||
| .decl n projs => some (n, projs)
|
||||
|
||||
@@ -245,7 +245,7 @@ We use this combinator to prevent stack overflows.
|
||||
@[inline] def withIncRecDepth [Monad m] [MonadError m] [MonadRecDepth m] (x : m α) : m α := do
|
||||
let curr ← MonadRecDepth.getRecDepth
|
||||
let max ← MonadRecDepth.getMaxRecDepth
|
||||
if curr == max then
|
||||
if max != 0 && curr == max then
|
||||
throwMaxRecDepthAt (← getRef)
|
||||
else
|
||||
MonadRecDepth.withRecDepth (curr+1) x
|
||||
|
||||
@@ -478,11 +478,11 @@ where
|
||||
}
|
||||
result? := some {
|
||||
parserState
|
||||
processedSnap := (← processHeader ⟨trimmedStx⟩ parserState)
|
||||
processedSnap := (← processHeader ⟨trimmedStx⟩ stx parserState)
|
||||
}
|
||||
}
|
||||
|
||||
processHeader (stx : HeaderSyntax) (parserState : Parser.ModuleParserState) :
|
||||
processHeader (stx : HeaderSyntax) (origStx : HeaderSyntax) (parserState : Parser.ModuleParserState) :
|
||||
LeanProcessingM (SnapshotTask HeaderProcessedSnapshot) := do
|
||||
let ctx ← read
|
||||
SnapshotTask.ofIO none none (.some ⟨0, ctx.endPos⟩) <|
|
||||
@@ -498,6 +498,7 @@ where
|
||||
let (headerEnv, msgLog) ← Elab.processHeaderCore (leakEnv := true)
|
||||
stx.startPos setup.imports setup.isModule setup.opts .empty ctx.toInputContext
|
||||
setup.trustLevel setup.plugins setup.mainModuleName setup.package? setup.importArts
|
||||
(headerStx? := stx) (origHeaderStx? := origStx)
|
||||
let stopTime := (← IO.monoNanosNow).toFloat / 1000000000
|
||||
let diagnostics := (← Snapshot.Diagnostics.ofMessageLog msgLog)
|
||||
if msgLog.hasErrors then
|
||||
|
||||
@@ -441,18 +441,27 @@ def Result.imax : Result → Result → Result
|
||||
| f, Result.imaxNode Fs => Result.imaxNode (f::Fs)
|
||||
| f₁, f₂ => Result.imaxNode [f₁, f₂]
|
||||
|
||||
def toResult (l : Level) (mvars : Bool) : Result :=
|
||||
structure Context where
|
||||
mvars : Bool
|
||||
lIndex? : LMVarId → Option Nat
|
||||
|
||||
abbrev M := ReaderM Context
|
||||
|
||||
def toResult (l : Level) : M Result := do
|
||||
match l with
|
||||
| zero => Result.num 0
|
||||
| succ l => Result.succ (toResult l mvars)
|
||||
| max l₁ l₂ => Result.max (toResult l₁ mvars) (toResult l₂ mvars)
|
||||
| imax l₁ l₂ => Result.imax (toResult l₁ mvars) (toResult l₂ mvars)
|
||||
| param n => Result.leaf n
|
||||
| zero => return Result.num 0
|
||||
| succ l => return Result.succ (← toResult l)
|
||||
| max l₁ l₂ => return Result.max (← toResult l₁) (← toResult l₂)
|
||||
| imax l₁ l₂ => return Result.imax (← toResult l₁) (← toResult l₂)
|
||||
| param n => return Result.leaf n
|
||||
| mvar n =>
|
||||
if mvars then
|
||||
Result.leaf <| n.name.replacePrefix `_uniq (Name.mkSimple "?u")
|
||||
if !(← read).mvars then
|
||||
return Result.leaf `_
|
||||
else if let some i := (← read).lIndex? n then
|
||||
return Result.leaf <| Name.num (Name.mkSimple "?u") (i + 1)
|
||||
else
|
||||
Result.leaf `_
|
||||
-- Undefined mvar, use internal name
|
||||
return Result.leaf <| n.name.replacePrefix `_uniq (Name.mkSimple "?_mvar")
|
||||
|
||||
private def parenIfFalse : Format → Bool → Format
|
||||
| f, true => f
|
||||
@@ -469,7 +478,7 @@ mutual
|
||||
| Result.offset f 0, r => format f r
|
||||
| Result.offset f (k+1), r =>
|
||||
let f' := format f false;
|
||||
parenIfFalse (f' ++ "+" ++ Std.format (k+1)) r
|
||||
parenIfFalse (f' ++ " + " ++ Std.format (k+1)) r
|
||||
| Result.maxNode fs, r => parenIfFalse (Format.group <| "max" ++ formatLst fs) r
|
||||
| Result.imaxNode fs, r => parenIfFalse (Format.group <| "imax" ++ formatLst fs) r
|
||||
end
|
||||
@@ -487,20 +496,20 @@ protected partial def Result.quote (r : Result) (prec : Nat) : Syntax.Level :=
|
||||
|
||||
end PP
|
||||
|
||||
protected def format (u : Level) (mvars : Bool) : Format :=
|
||||
(PP.toResult u mvars).format true
|
||||
protected def format (u : Level) (mvars : Bool) (lIndex? : LMVarId → Option Nat) : Format :=
|
||||
(PP.toResult u) |>.run { mvars, lIndex? } |>.format true
|
||||
|
||||
instance : ToFormat Level where
|
||||
format u := Level.format u (mvars := true)
|
||||
format u := Level.format u (mvars := true) (lIndex? := fun _ => none)
|
||||
|
||||
instance : ToString Level where
|
||||
toString u := Format.pretty (format u)
|
||||
|
||||
protected def quote (u : Level) (prec : Nat := 0) (mvars : Bool := true) : Syntax.Level :=
|
||||
(PP.toResult u (mvars := mvars)).quote prec
|
||||
protected def quote (u : Level) (prec : Nat := 0) (mvars : Bool := true) (lIndex? : LMVarId → Option Nat) : Syntax.Level :=
|
||||
(PP.toResult u) |>.run { mvars, lIndex? } |>.quote prec
|
||||
|
||||
instance : Quote Level `level where
|
||||
quote := Level.quote
|
||||
quote := Level.quote (lIndex? := fun _ => none)
|
||||
|
||||
end Level
|
||||
|
||||
|
||||
@@ -91,10 +91,10 @@ end FoldRelevantConstantsImpl
|
||||
@[implemented_by FoldRelevantConstantsImpl.foldUnsafe]
|
||||
public opaque foldRelevantConstants {α : Type} (e : Expr) (init : α) (f : Name → α → MetaM α) : MetaM α := pure init
|
||||
|
||||
/-- Collect the constants occuring in `e` (once each), skipping instance arguments and proofs. -/
|
||||
/-- Collect the constants occurring in `e` (once each), skipping instance arguments and proofs. -/
|
||||
public def relevantConstants (e : Expr) : MetaM (Array Name) := foldRelevantConstants e #[] (fun n ns => return ns.push n)
|
||||
|
||||
/-- Collect the constants occuring in `e` (once each), skipping instance arguments and proofs. -/
|
||||
/-- Collect the constants occurring in `e` (once each), skipping instance arguments and proofs. -/
|
||||
public def relevantConstantsAsSet (e : Expr) : MetaM NameSet := foldRelevantConstants e ∅ (fun n ns => return ns.insert n)
|
||||
|
||||
end Lean.Expr
|
||||
|
||||
@@ -66,7 +66,11 @@ Helper function for running `MetaM` code during module export, when there is not
|
||||
Panics on errors.
|
||||
-/
|
||||
unsafe def _root_.Lean.Environment.unsafeRunMetaM [Inhabited α] (env : Environment) (x : MetaM α) : α :=
|
||||
match unsafeEIO ((((withoutExporting x).run' {} {}).run' { fileName := "symbolFrequency", fileMap := default } { env })) with
|
||||
match unsafeEIO ((((withoutExporting x).run' {} {}).run'
|
||||
{ fileName := "symbolFrequency", fileMap := default
|
||||
-- avoid triggering since limit cannot be raised here
|
||||
maxHeartbeats := 0 }
|
||||
{ env })) with
|
||||
| Except.ok a => a
|
||||
| Except.error ex => panic! match unsafeIO ex.toMessageData.toString with
|
||||
| Except.ok s => s
|
||||
|
||||
@@ -112,15 +112,37 @@ builtin_initialize
|
||||
def lint (stx : Syntax) (msg : String) : CommandElabM Unit :=
|
||||
logLint linter.missingDocs stx m!"missing doc string for {msg}"
|
||||
|
||||
def lintEmpty (stx : Syntax) (msg : String) : CommandElabM Unit :=
|
||||
logLint linter.missingDocs stx m!"empty doc string for {msg}"
|
||||
|
||||
def lintNamed (stx : Syntax) (msg : String) : CommandElabM Unit :=
|
||||
lint stx s!"{msg} {stx.getId}"
|
||||
|
||||
def lintEmptyNamed (stx : Syntax) (msg : String) : CommandElabM Unit :=
|
||||
lintEmpty stx s!"{msg} {stx.getId}"
|
||||
|
||||
def lintField (parent stx : Syntax) (msg : String) : CommandElabM Unit :=
|
||||
lint stx s!"{msg} {parent.getId}.{stx.getId}"
|
||||
|
||||
def lintEmptyField (parent stx : Syntax) (msg : String) : CommandElabM Unit :=
|
||||
lintEmpty stx s!"{msg} {parent.getId}.{stx.getId}"
|
||||
|
||||
def lintStructField (parent stx : Syntax) (msg : String) : CommandElabM Unit :=
|
||||
lint stx s!"{msg} {parent.getId}.{stx.getId}"
|
||||
|
||||
private def isEmptyDocString (docOpt : Syntax) : CommandElabM Bool := do
|
||||
if docOpt.isNone then return false
|
||||
let docStx : TSyntax `Lean.Parser.Command.docComment := ⟨docOpt[0]⟩
|
||||
-- Verso doc comments with interpolated content cannot be extracted as plain text,
|
||||
-- but they are clearly not empty.
|
||||
if let .node _ `Lean.Parser.Command.versoCommentBody _ := docStx.raw[1] then
|
||||
if !docStx.raw[1][0].isAtom then return false
|
||||
let text ← getDocStringText docStx
|
||||
return text.trimAscii.isEmpty
|
||||
|
||||
def isMissingDoc (docOpt : Syntax) : CommandElabM Bool := do
|
||||
return docOpt.isNone || (← isEmptyDocString docOpt)
|
||||
|
||||
def hasInheritDoc (attrs : Syntax) : Bool :=
|
||||
attrs[0][1].getSepArgs.any fun attr =>
|
||||
attr[1].isOfKind ``Parser.Attr.simple &&
|
||||
@@ -130,38 +152,68 @@ def hasTacticAlt (attrs : Syntax) : Bool :=
|
||||
attrs[0][1].getSepArgs.any fun attr =>
|
||||
attr[1].isOfKind ``Parser.Attr.tactic_alt
|
||||
|
||||
def declModifiersPubNoDoc (mods : Syntax) : CommandElabM Bool := do
|
||||
def declModifiersDocStatus (mods : Syntax) : CommandElabM (Option Bool) := do
|
||||
let isPublic := if (← getEnv).header.isModule && !(← getScope).isPublic then
|
||||
mods[2][0].getKind == ``Command.public else
|
||||
mods[2][0].getKind != ``Command.private
|
||||
return isPublic && mods[0].isNone && !hasInheritDoc mods[1]
|
||||
if !isPublic || hasInheritDoc mods[1] then return none
|
||||
if mods[0].isNone then return some false
|
||||
if (← isEmptyDocString mods[0]) then return some true
|
||||
return none
|
||||
|
||||
def lintDeclHead (k : SyntaxNodeKind) (id : Syntax) : CommandElabM Unit := do
|
||||
if k == ``«abbrev» then lintNamed id "public abbrev"
|
||||
else if k == ``definition then lintNamed id "public def"
|
||||
else if k == ``«opaque» then lintNamed id "public opaque"
|
||||
else if k == ``«axiom» then lintNamed id "public axiom"
|
||||
else if k == ``«inductive» then lintNamed id "public inductive"
|
||||
else if k == ``classInductive then lintNamed id "public inductive"
|
||||
else if k == ``«structure» then lintNamed id "public structure"
|
||||
def declModifiersPubNoDoc (mods : Syntax) : CommandElabM Bool := do
|
||||
return (← declModifiersDocStatus mods).isSome
|
||||
|
||||
private def lintDocStatus (isEmpty : Bool) (stx : Syntax) (msg : String) : CommandElabM Unit :=
|
||||
if isEmpty then lintEmpty stx msg else lint stx msg
|
||||
|
||||
private def lintDocStatusNamed (isEmpty : Bool) (stx : Syntax) (msg : String) : CommandElabM Unit :=
|
||||
if isEmpty then lintEmptyNamed stx msg else lintNamed stx msg
|
||||
|
||||
private def lintDocStatusField (isEmpty : Bool) (parent stx : Syntax) (msg : String) :
|
||||
CommandElabM Unit :=
|
||||
if isEmpty then lintEmptyField parent stx msg else lintField parent stx msg
|
||||
|
||||
def lintDeclHead (k : SyntaxNodeKind) (id : Syntax) (isEmpty : Bool := false) :
|
||||
CommandElabM Unit := do
|
||||
if k == ``«abbrev» then lintDocStatusNamed isEmpty id "public abbrev"
|
||||
else if k == ``definition then lintDocStatusNamed isEmpty id "public def"
|
||||
else if k == ``«opaque» then lintDocStatusNamed isEmpty id "public opaque"
|
||||
else if k == ``«axiom» then lintDocStatusNamed isEmpty id "public axiom"
|
||||
else if k == ``«inductive» then lintDocStatusNamed isEmpty id "public inductive"
|
||||
else if k == ``classInductive then lintDocStatusNamed isEmpty id "public inductive"
|
||||
else if k == ``«structure» then lintDocStatusNamed isEmpty id "public structure"
|
||||
|
||||
private def docOptStatus (docOpt attrs : Syntax) (checkTacticAlt := false) :
|
||||
CommandElabM (Option Bool) := do
|
||||
if hasInheritDoc attrs then return none
|
||||
if checkTacticAlt && hasTacticAlt attrs then return none
|
||||
if docOpt.isNone then return some false
|
||||
if (← isEmptyDocString docOpt) then return some true
|
||||
return none
|
||||
|
||||
@[builtin_missing_docs_handler declaration]
|
||||
def checkDecl : SimpleHandler := fun stx => do
|
||||
let head := stx[0]; let rest := stx[1]
|
||||
if head[2][0].getKind == ``Command.private then return -- not private
|
||||
let k := rest.getKind
|
||||
if (← declModifiersPubNoDoc head) then -- no doc string
|
||||
lintDeclHead k rest[1][0]
|
||||
if let some isEmpty ← declModifiersDocStatus head then
|
||||
lintDeclHead k rest[1][0] isEmpty
|
||||
if k == ``«inductive» || k == ``classInductive then
|
||||
for stx in rest[4].getArgs do
|
||||
let head := stx[2]
|
||||
if stx[0].isNone && (← declModifiersPubNoDoc head) then
|
||||
lintField rest[1][0] stx[3] "public constructor"
|
||||
-- Constructor has two doc comment positions: the leading one before `|` (stx[0])
|
||||
-- and the one inside declModifiers (head[0]). If either is non-empty, skip.
|
||||
let leadingEmpty ← isEmptyDocString stx[0]
|
||||
if !stx[0].isNone && !leadingEmpty then
|
||||
pure () -- constructor has a non-empty leading doc comment
|
||||
else if let some modsEmpty ← declModifiersDocStatus head then
|
||||
lintDocStatusField (leadingEmpty || modsEmpty) rest[1][0] stx[3] "public constructor"
|
||||
unless rest[5].isNone do
|
||||
for stx in rest[5][0][1].getArgs do
|
||||
let head := stx[0]
|
||||
if (← declModifiersPubNoDoc head) then -- no doc string
|
||||
lintField rest[1][0] stx[1] "computed field"
|
||||
if let some isEmpty ← declModifiersDocStatus head then
|
||||
lintDocStatusField isEmpty rest[1][0] stx[1] "computed field"
|
||||
else if rest.getKind == ``«structure» then
|
||||
unless rest[4][2].isNone do
|
||||
let redecls : Std.HashSet String.Pos.Raw :=
|
||||
@@ -173,45 +225,52 @@ def checkDecl : SimpleHandler := fun stx => do
|
||||
else s
|
||||
else s
|
||||
let parent := rest[1][0]
|
||||
let lint1 stx := do
|
||||
let lint1 isEmpty stx := do
|
||||
if let some range := stx.getRange? then
|
||||
if redecls.contains range.start then return
|
||||
lintField parent stx "public field"
|
||||
lintDocStatusField isEmpty parent stx "public field"
|
||||
for stx in rest[4][2][0].getArgs do
|
||||
let head := stx[0]
|
||||
if (← declModifiersPubNoDoc head) then
|
||||
if let some isEmpty ← declModifiersDocStatus head then
|
||||
if stx.getKind == ``structSimpleBinder then
|
||||
lint1 stx[1]
|
||||
lint1 isEmpty stx[1]
|
||||
else
|
||||
for stx in stx[2].getArgs do
|
||||
lint1 stx
|
||||
lint1 isEmpty stx
|
||||
|
||||
@[builtin_missing_docs_handler «initialize»]
|
||||
def checkInit : SimpleHandler := fun stx => do
|
||||
if !stx[2].isNone && (← declModifiersPubNoDoc stx[0]) then
|
||||
lintNamed stx[2][0] "initializer"
|
||||
if !stx[2].isNone then
|
||||
if let some isEmpty ← declModifiersDocStatus stx[0] then
|
||||
lintDocStatusNamed isEmpty stx[2][0] "initializer"
|
||||
|
||||
@[builtin_missing_docs_handler «notation»]
|
||||
def checkNotation : SimpleHandler := fun stx => do
|
||||
if stx[0].isNone && stx[2][0][0].getKind != ``«local» && !hasInheritDoc stx[1] then
|
||||
if stx[5].isNone then lint stx[3] "notation"
|
||||
else lintNamed stx[5][0][3] "notation"
|
||||
if stx[2][0][0].getKind != ``«local» then
|
||||
if let some isEmpty ← docOptStatus stx[0] stx[1] then
|
||||
if stx[5].isNone then lintDocStatus isEmpty stx[3] "notation"
|
||||
else lintDocStatusNamed isEmpty stx[5][0][3] "notation"
|
||||
|
||||
@[builtin_missing_docs_handler «mixfix»]
|
||||
def checkMixfix : SimpleHandler := fun stx => do
|
||||
if stx[0].isNone && stx[2][0][0].getKind != ``«local» && !hasInheritDoc stx[1] then
|
||||
if stx[5].isNone then lint stx[3] stx[3][0].getAtomVal
|
||||
else lintNamed stx[5][0][3] stx[3][0].getAtomVal
|
||||
if stx[2][0][0].getKind != ``«local» then
|
||||
if let some isEmpty ← docOptStatus stx[0] stx[1] then
|
||||
if stx[5].isNone then lintDocStatus isEmpty stx[3] stx[3][0].getAtomVal
|
||||
else lintDocStatusNamed isEmpty stx[5][0][3] stx[3][0].getAtomVal
|
||||
|
||||
@[builtin_missing_docs_handler «syntax»]
|
||||
def checkSyntax : SimpleHandler := fun stx => do
|
||||
if stx[0].isNone && stx[2][0][0].getKind != ``«local» && !hasInheritDoc stx[1] && !hasTacticAlt stx[1] then
|
||||
if stx[5].isNone then lint stx[3] "syntax"
|
||||
else lintNamed stx[5][0][3] "syntax"
|
||||
if stx[2][0][0].getKind != ``«local» then
|
||||
if let some isEmpty ← docOptStatus stx[0] stx[1] (checkTacticAlt := true) then
|
||||
if stx[5].isNone then lintDocStatus isEmpty stx[3] "syntax"
|
||||
else lintDocStatusNamed isEmpty stx[5][0][3] "syntax"
|
||||
|
||||
def mkSimpleHandler (name : String) (declNameStxIdx := 2) : SimpleHandler := fun stx => do
|
||||
if stx[0].isNone then
|
||||
lintNamed stx[declNameStxIdx] name
|
||||
if (← isMissingDoc stx[0]) then
|
||||
if (← isEmptyDocString stx[0]) then
|
||||
lintEmptyNamed stx[declNameStxIdx] name
|
||||
else
|
||||
lintNamed stx[declNameStxIdx] name
|
||||
|
||||
@[builtin_missing_docs_handler syntaxAbbrev]
|
||||
def checkSyntaxAbbrev : SimpleHandler := mkSimpleHandler "syntax"
|
||||
@@ -221,20 +280,22 @@ def checkSyntaxCat : SimpleHandler := mkSimpleHandler "syntax category"
|
||||
|
||||
@[builtin_missing_docs_handler «macro»]
|
||||
def checkMacro : SimpleHandler := fun stx => do
|
||||
if stx[0].isNone && stx[2][0][0].getKind != ``«local» && !hasInheritDoc stx[1] && !hasTacticAlt stx[1] then
|
||||
if stx[5].isNone then lint stx[3] "macro"
|
||||
else lintNamed stx[5][0][3] "macro"
|
||||
if stx[2][0][0].getKind != ``«local» then
|
||||
if let some isEmpty ← docOptStatus stx[0] stx[1] (checkTacticAlt := true) then
|
||||
if stx[5].isNone then lintDocStatus isEmpty stx[3] "macro"
|
||||
else lintDocStatusNamed isEmpty stx[5][0][3] "macro"
|
||||
|
||||
@[builtin_missing_docs_handler «elab»]
|
||||
def checkElab : SimpleHandler := fun stx => do
|
||||
if stx[0].isNone && stx[2][0][0].getKind != ``«local» && !hasInheritDoc stx[1] && !hasTacticAlt stx[1] then
|
||||
if stx[5].isNone then lint stx[3] "elab"
|
||||
else lintNamed stx[5][0][3] "elab"
|
||||
if stx[2][0][0].getKind != ``«local» then
|
||||
if let some isEmpty ← docOptStatus stx[0] stx[1] (checkTacticAlt := true) then
|
||||
if stx[5].isNone then lintDocStatus isEmpty stx[3] "elab"
|
||||
else lintDocStatusNamed isEmpty stx[5][0][3] "elab"
|
||||
|
||||
@[builtin_missing_docs_handler classAbbrev]
|
||||
def checkClassAbbrev : SimpleHandler := fun stx => do
|
||||
if (← declModifiersPubNoDoc stx[0]) then
|
||||
lintNamed stx[3] "class abbrev"
|
||||
if let some isEmpty ← declModifiersDocStatus stx[0] then
|
||||
lintDocStatusNamed isEmpty stx[3] "class abbrev"
|
||||
|
||||
@[builtin_missing_docs_handler Parser.Tactic.declareSimpLikeTactic]
|
||||
def checkSimpLike : SimpleHandler := mkSimpleHandler "simp-like tactic"
|
||||
@@ -244,8 +305,8 @@ def checkRegisterBuiltinOption : SimpleHandler := mkSimpleHandler (declNameStxId
|
||||
|
||||
@[builtin_missing_docs_handler Option.registerOption]
|
||||
def checkRegisterOption : SimpleHandler := fun stx => do
|
||||
if (← declModifiersPubNoDoc stx[0]) then
|
||||
lintNamed stx[2] "option"
|
||||
if let some isEmpty ← declModifiersDocStatus stx[0] then
|
||||
lintDocStatusNamed isEmpty stx[2] "option"
|
||||
|
||||
@[builtin_missing_docs_handler registerSimpAttr]
|
||||
def checkRegisterSimpAttr : SimpleHandler := mkSimpleHandler "simp attr"
|
||||
|
||||
@@ -244,7 +244,7 @@ def ofLevel (l : Level) : MessageData :=
|
||||
.ofLazy
|
||||
(fun ctx? => do
|
||||
let msg ← ofFormat <$> match ctx? with
|
||||
| .none => pure (format l)
|
||||
| .none => pure (l.format true (fun _ => none))
|
||||
| .some ctx => ppLevel ctx l
|
||||
return Dynamic.mk msg)
|
||||
(fun _ => false)
|
||||
|
||||
@@ -6,6 +6,7 @@ Authors: Leonardo de Moura
|
||||
module
|
||||
prelude
|
||||
public import Lean.Meta.Basic
|
||||
public import Lean.Meta.HasAssignableMVar
|
||||
public import Lean.Meta.LevelDefEq
|
||||
public import Lean.Meta.WHNF
|
||||
public import Lean.Meta.InferType
|
||||
|
||||
@@ -70,6 +70,7 @@ structure Context where
|
||||
abbrev M := ReaderT Context $ MonadCacheT ExprStructEq Expr MetaM
|
||||
|
||||
partial def visit (e : Expr) : M Expr := do
|
||||
checkSystem "abstract nested proofs"
|
||||
if e.isAtomic then
|
||||
pure e
|
||||
else
|
||||
|
||||
@@ -8,6 +8,7 @@ prelude
|
||||
public import Lean.Meta.SynthInstance
|
||||
public import Lean.Meta.DecLevel
|
||||
import Lean.Meta.CtorRecognizer
|
||||
public import Lean.Meta.HasAssignableMVar
|
||||
import Lean.Structure
|
||||
import Init.Omega
|
||||
public section
|
||||
|
||||
54
src/Lean/Meta/HasAssignableMVar.lean
Normal file
54
src/Lean/Meta/HasAssignableMVar.lean
Normal file
@@ -0,0 +1,54 @@
|
||||
/-
|
||||
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Leonardo de Moura
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Lean.Meta.Basic
|
||||
|
||||
public section
|
||||
|
||||
open Lean
|
||||
|
||||
namespace Lean.Meta
|
||||
|
||||
def hasAssignableLevelMVar : Level → MetaM Bool
|
||||
| .succ lvl => pure lvl.hasMVar <&&> hasAssignableLevelMVar lvl
|
||||
| .max lvl₁ lvl₂ => (pure lvl₁.hasMVar <&&> hasAssignableLevelMVar lvl₁) <||> (pure lvl₂.hasMVar <&&> hasAssignableLevelMVar lvl₂)
|
||||
| .imax lvl₁ lvl₂ => (pure lvl₁.hasMVar <&&> hasAssignableLevelMVar lvl₁) <||> (pure lvl₂.hasMVar <&&> hasAssignableLevelMVar lvl₂)
|
||||
| .mvar mvarId => isLevelMVarAssignable mvarId
|
||||
| .zero => return false
|
||||
| .param _ => return false
|
||||
|
||||
/-- Return `true` iff expression contains a metavariable that can be assigned. -/
|
||||
partial def hasAssignableMVar (e : Expr) : MetaM Bool :=
|
||||
if !e.hasMVar then
|
||||
return false
|
||||
else
|
||||
go e |>.run' {}
|
||||
where
|
||||
go (e : Expr) : StateRefT ExprSet MetaM Bool :=
|
||||
match e with
|
||||
| .const _ lvls => lvls.anyM (liftM <| hasAssignableLevelMVar ·)
|
||||
| .sort lvl => liftM <| hasAssignableLevelMVar lvl
|
||||
| .app f a => do checkSystem "hasAssignableMVar"; visit f <||> visit a
|
||||
| .letE _ t v b _ => do checkSystem "hasAssignableMVar"; visit t <||> visit v <||> visit b
|
||||
| .forallE _ d b _ => do checkSystem "hasAssignableMVar"; visit d <||> visit b
|
||||
| .lam _ d b _ => do checkSystem "hasAssignableMVar"; visit d <||> visit b
|
||||
| .fvar _ => return false
|
||||
| .bvar _ => return false
|
||||
| .lit _ => return false
|
||||
| .mdata _ e => visit e
|
||||
| .proj _ _ e => visit e
|
||||
| .mvar mvarId => mvarId.isAssignable
|
||||
visit (e : Expr) : StateRefT ExprSet MetaM Bool := do
|
||||
if !e.hasMVar then return false
|
||||
if (← get).contains e then return false
|
||||
modify fun s => s.insert e
|
||||
go e
|
||||
|
||||
end Lean.Meta
|
||||
|
||||
end
|
||||
@@ -8,6 +8,7 @@ module
|
||||
prelude
|
||||
public import Lean.Util.CollectMVars
|
||||
public import Lean.Meta.DecLevel
|
||||
public import Lean.Meta.HasAssignableMVar
|
||||
|
||||
public section
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import Lean.Meta.Sym.AlphaShareBuilder
|
||||
import Lean.Meta.Sym.Offset
|
||||
import Lean.Meta.Sym.Eta
|
||||
import Lean.Meta.Sym.Util
|
||||
import Lean.Meta.HasAssignableMVar
|
||||
import Init.Data.List.MapIdx
|
||||
import Init.Data.Nat.Linear
|
||||
import Std.Do.Triple.Basic
|
||||
|
||||
@@ -14,6 +14,7 @@ import Lean.Meta.Tactic.Grind.SynthInstance
|
||||
import Lean.Meta.Tactic.Grind.Simp
|
||||
import Init.Grind.Util
|
||||
import Init.Omega
|
||||
public import Lean.Meta.HasAssignableMVar
|
||||
public section
|
||||
namespace Lean.Meta.Grind
|
||||
namespace EMatch
|
||||
|
||||
@@ -714,7 +714,6 @@ where
|
||||
set_option compiler.ignoreBorrowAnnotation true in
|
||||
@[export lean_simp]
|
||||
def simpImpl (e : Expr) : SimpM Result := withIncRecDepth do
|
||||
checkSystem "simp"
|
||||
if (← isProof e) then
|
||||
return { expr := e }
|
||||
trace[Meta.Tactic.simp.heads] "{repr e.toHeadIndex}"
|
||||
|
||||
@@ -12,6 +12,7 @@ public import Lean.Meta.Tactic.Simp.Arith
|
||||
public import Lean.Meta.Tactic.Simp.Attr
|
||||
public import Lean.Meta.BinderNameHint
|
||||
import Lean.Meta.WHNF
|
||||
public import Lean.Meta.HasAssignableMVar
|
||||
public section
|
||||
namespace Lean.Meta.Simp
|
||||
/--
|
||||
@@ -99,7 +100,7 @@ where
|
||||
if (← withReducibleAndInstances <| isDefEq x val) then
|
||||
return true
|
||||
else
|
||||
trace[Meta.Tactic.simp.discharge] "{← ppOrigin thmId}, failed to assign instance{indentExpr type}\nsythesized value{indentExpr val}\nis not definitionally equal to{indentExpr x}"
|
||||
trace[Meta.Tactic.simp.discharge] "{← ppOrigin thmId}, failed to assign instance{indentExpr type}\nsynthesized value{indentExpr val}\nis not definitionally equal to{indentExpr x}"
|
||||
return false
|
||||
| _ =>
|
||||
trace[Meta.Tactic.simp.discharge] "{← ppOrigin thmId}, failed to synthesize instance{indentExpr type}"
|
||||
@@ -218,6 +219,7 @@ where
|
||||
else
|
||||
let candidates := candidates.insertionSort fun e₁ e₂ => e₁.1.priority > e₂.1.priority
|
||||
for (thm, numExtraArgs) in candidates do
|
||||
checkSystem "simp"
|
||||
if inErasedSet thm then continue
|
||||
if rflOnly then
|
||||
unless thm.rfl do
|
||||
@@ -245,6 +247,7 @@ where
|
||||
else
|
||||
let candidates := candidates.insertionSort fun e₁ e₂ => e₁.priority > e₂.priority
|
||||
for thm in candidates do
|
||||
checkSystem "simp"
|
||||
unless inErasedSet thm || (rflOnly && !thm.rfl) do
|
||||
let result? ← withNewMCtxDepth do
|
||||
let val ← thm.getValue
|
||||
|
||||
@@ -722,6 +722,7 @@ def simpAppUsingCongr (e : Expr) : SimpM Result := do
|
||||
if i == 0 then
|
||||
simp f
|
||||
else
|
||||
checkSystem "simp"
|
||||
let i := i - 1
|
||||
let .app f a := e | unreachable!
|
||||
let fr ← visit f i
|
||||
|
||||
@@ -50,6 +50,7 @@ partial def transform {m} [Monad m] [MonadLiftT CoreM m] [MonadControlT CoreM m]
|
||||
let _ : MonadLiftT (ST IO.RealWorld) m := { monadLift := fun x => liftM (m := CoreM) (liftM (m := ST IO.RealWorld) x) }
|
||||
let rec visit (e : Expr) : MonadCacheT ExprStructEq Expr m Expr :=
|
||||
checkCache { val := e : ExprStructEq } fun _ => Core.withIncRecDepth do
|
||||
Core.checkSystem "transform"
|
||||
let rec visitPost (e : Expr) : MonadCacheT ExprStructEq Expr m Expr := do
|
||||
match (← post e) with
|
||||
| .done e => pure e
|
||||
@@ -107,6 +108,7 @@ partial def transformWithCache {m} [Monad m] [MonadLiftT MetaM m] [MonadControlT
|
||||
let _ : MonadLiftT (ST IO.RealWorld) m := { monadLift := fun x => liftM (m := MetaM) (liftM (m := ST IO.RealWorld) x) }
|
||||
let rec visit (e : Expr) : MonadCacheT ExprStructEq Expr m Expr :=
|
||||
checkCache { val := e : ExprStructEq } fun _ => Meta.withIncRecDepth do
|
||||
(Core.checkSystem "transform" : MetaM Unit)
|
||||
let rec visitPost (e : Expr) : MonadCacheT ExprStructEq Expr m Expr := do
|
||||
match (← post e) with
|
||||
| .done e => pure e
|
||||
|
||||
@@ -650,7 +650,7 @@ expand let-expressions, expand assigned meta-variables, unfold aux declarations.
|
||||
partial def whnfCore (e : Expr) : MetaM Expr :=
|
||||
go e
|
||||
where
|
||||
go (e : Expr) : MetaM Expr :=
|
||||
go (e : Expr) : MetaM Expr := do
|
||||
whnfEasyCases e fun e => do
|
||||
trace[Meta.whnf] e
|
||||
match e with
|
||||
|
||||
@@ -247,6 +247,18 @@ very simple unification and/or non-nested TC. So, if the "app builder" becomes a
|
||||
we may solve the issue by implementing `isDefEqCheap` that never invokes TC and uses tmp metavars.
|
||||
-/
|
||||
|
||||
structure LevelMetavarDecl where
|
||||
/-- The nesting depth of this metavariable. We do not want
|
||||
unification subproblems to influence the results of parent
|
||||
problems. The depth keeps track of this information and ensures
|
||||
that unification subproblems cannot leak information out, by unifying
|
||||
based on depth. -/
|
||||
depth : Nat
|
||||
/-- This field tracks how old a metavariable is. It is set using a counter at `MetavarContext`.
|
||||
We primarily use the index of a level metavariable for pretty printing. -/
|
||||
index : Nat
|
||||
deriving Inhabited
|
||||
|
||||
/--
|
||||
`LocalInstance` represents a local typeclass instance registered by and for
|
||||
the elaborator. It stores the name of the typeclass in `className`, and the
|
||||
@@ -309,7 +321,8 @@ structure MetavarDecl where
|
||||
kind : MetavarKind
|
||||
/-- See comment at `CheckAssignment` `Meta/ExprDefEq.lean` -/
|
||||
numScopeArgs : Nat := 0
|
||||
/-- We use this field to track how old a metavariable is. It is set using a counter at `MetavarContext` -/
|
||||
/-- We use this field to track how old a metavariable is. It is set using a counter at `MetavarContext`.
|
||||
We also use it for pretty printing anonymous metavariables. -/
|
||||
index : Nat
|
||||
deriving Inhabited
|
||||
|
||||
@@ -340,9 +353,12 @@ structure MetavarContext where
|
||||
depth : Nat := 0
|
||||
/-- At what depth level mvars can be assigned. -/
|
||||
levelAssignDepth : Nat := 0
|
||||
/-- Counter for setting the field `index` at `LevelMetavarDecl`. -/
|
||||
lmvarCounter : Nat := 0
|
||||
/-- Counter for setting the field `index` at `MetavarDecl` -/
|
||||
mvarCounter : Nat := 0
|
||||
lDepth : PersistentHashMap LMVarId Nat := {}
|
||||
/-- Level metavariable declarations. -/
|
||||
lDecls : PersistentHashMap LMVarId LevelMetavarDecl := {}
|
||||
/-- Metavariable declarations. -/
|
||||
decls : PersistentHashMap MVarId MetavarDecl := {}
|
||||
/-- Index mapping user-friendly names to ids. -/
|
||||
@@ -444,11 +460,21 @@ def _root_.Lean.MVarId.isAssignedOrDelayedAssigned [Monad m] [MonadMCtx m] (mvar
|
||||
let mctx ← getMCtx
|
||||
return mctx.eAssignment.contains mvarId || mctx.dAssignment.contains mvarId
|
||||
|
||||
def MetavarContext.findLevelDecl? (mctx : MetavarContext) (mvarId : LMVarId) : Option LevelMetavarDecl :=
|
||||
mctx.lDecls.find? mvarId
|
||||
|
||||
def MetavarContext.getLevelDecl (mctx : MetavarContext) (mvarId : LMVarId) : LevelMetavarDecl :=
|
||||
match mctx.lDecls.find? mvarId with
|
||||
| some decl => decl
|
||||
| none => panic! s!"unknown universe metavariable {mvarId.name}"
|
||||
|
||||
def isLevelMVarAssignable [Monad m] [MonadMCtx m] (mvarId : LMVarId) : m Bool := do
|
||||
let mctx ← getMCtx
|
||||
match mctx.lDepth.find? mvarId with
|
||||
| some d => return d >= mctx.levelAssignDepth
|
||||
| _ => panic! s!"unknown universe metavariable {mvarId.name}"
|
||||
let decl := mctx.getLevelDecl mvarId
|
||||
return decl.depth >= mctx.levelAssignDepth
|
||||
|
||||
def MetavarContext.findDecl? (mctx : MetavarContext) (mvarId : MVarId) : Option MetavarDecl :=
|
||||
mctx.decls.find? mvarId
|
||||
|
||||
def MetavarContext.getDecl (mctx : MetavarContext) (mvarId : MVarId) : MetavarDecl :=
|
||||
match mctx.decls.find? mvarId with
|
||||
@@ -484,30 +510,6 @@ def hasAssignedMVar [Monad m] [MonadMCtx m] : Expr → m Bool
|
||||
| .proj _ _ e => pure e.hasMVar <&&> hasAssignedMVar e
|
||||
| .mvar mvarId => mvarId.isAssigned <||> mvarId.isDelayedAssigned
|
||||
|
||||
/-- Return true iff the given level contains a metavariable that can be assigned. -/
|
||||
def hasAssignableLevelMVar [Monad m] [MonadMCtx m] : Level → m Bool
|
||||
| .succ lvl => pure lvl.hasMVar <&&> hasAssignableLevelMVar lvl
|
||||
| .max lvl₁ lvl₂ => (pure lvl₁.hasMVar <&&> hasAssignableLevelMVar lvl₁) <||> (pure lvl₂.hasMVar <&&> hasAssignableLevelMVar lvl₂)
|
||||
| .imax lvl₁ lvl₂ => (pure lvl₁.hasMVar <&&> hasAssignableLevelMVar lvl₁) <||> (pure lvl₂.hasMVar <&&> hasAssignableLevelMVar lvl₂)
|
||||
| .mvar mvarId => isLevelMVarAssignable mvarId
|
||||
| .zero => return false
|
||||
| .param _ => return false
|
||||
|
||||
/-- Return `true` iff expression contains a metavariable that can be assigned. -/
|
||||
def hasAssignableMVar [Monad m] [MonadMCtx m] : Expr → m Bool
|
||||
| .const _ lvls => lvls.anyM hasAssignableLevelMVar
|
||||
| .sort lvl => hasAssignableLevelMVar lvl
|
||||
| .app f a => (pure f.hasMVar <&&> hasAssignableMVar f) <||> (pure a.hasMVar <&&> hasAssignableMVar a)
|
||||
| .letE _ t v b _ => (pure t.hasMVar <&&> hasAssignableMVar t) <||> (pure v.hasMVar <&&> hasAssignableMVar v) <||> (pure b.hasMVar <&&> hasAssignableMVar b)
|
||||
| .forallE _ d b _ => (pure d.hasMVar <&&> hasAssignableMVar d) <||> (pure b.hasMVar <&&> hasAssignableMVar b)
|
||||
| .lam _ d b _ => (pure d.hasMVar <&&> hasAssignableMVar d) <||> (pure b.hasMVar <&&> hasAssignableMVar b)
|
||||
| .fvar _ => return false
|
||||
| .bvar _ => return false
|
||||
| .lit _ => return false
|
||||
| .mdata _ e => pure e.hasMVar <&&> hasAssignableMVar e
|
||||
| .proj _ _ e => pure e.hasMVar <&&> hasAssignableMVar e
|
||||
| .mvar mvarId => mvarId.isAssignable
|
||||
|
||||
/--
|
||||
Add `mvarId := u` to the universe metavariable assignment.
|
||||
This method does not check whether `mvarId` is already assigned, nor does it check whether
|
||||
@@ -826,10 +828,11 @@ def addExprMVarDeclExp (mctx : MetavarContext) (mvarId : MVarId) (userName : Nam
|
||||
It is used to implement actions in the monads `MetaM`, `ElabM` and `TacticM`.
|
||||
It should not be used directly since the argument `(mvarId : MVarId)` is assumed to be "unique". -/
|
||||
def addLevelMVarDecl (mctx : MetavarContext) (mvarId : LMVarId) : MetavarContext :=
|
||||
{ mctx with lDepth := mctx.lDepth.insert mvarId mctx.depth }
|
||||
|
||||
def findDecl? (mctx : MetavarContext) (mvarId : MVarId) : Option MetavarDecl :=
|
||||
mctx.decls.find? mvarId
|
||||
{ mctx with
|
||||
lmvarCounter := mctx.lmvarCounter + 1
|
||||
lDecls := mctx.lDecls.insert mvarId {
|
||||
depth := mctx.depth
|
||||
index := mctx.lmvarCounter } }
|
||||
|
||||
def findUserName? (mctx : MetavarContext) (userName : Name) : Option MVarId :=
|
||||
mctx.userNames.find? userName
|
||||
@@ -909,12 +912,13 @@ def setFVarBinderInfo (mctx : MetavarContext) (mvarId : MVarId)
|
||||
mctx.modifyExprMVarLCtx mvarId (·.setBinderInfo fvarId bi)
|
||||
|
||||
def findLevelDepth? (mctx : MetavarContext) (mvarId : LMVarId) : Option Nat :=
|
||||
mctx.lDepth.find? mvarId
|
||||
(mctx.findLevelDecl? mvarId).map LevelMetavarDecl.depth
|
||||
|
||||
def getLevelDepth (mctx : MetavarContext) (mvarId : LMVarId) : Nat :=
|
||||
match mctx.findLevelDepth? mvarId with
|
||||
| some d => d
|
||||
| none => panic! "unknown metavariable"
|
||||
(mctx.getLevelDecl mvarId).depth
|
||||
|
||||
def findLevelIndex? (mctx : MetavarContext) (mvarId : LMVarId) : Option Nat :=
|
||||
(mctx.findLevelDecl? mvarId).map LevelMetavarDecl.index
|
||||
|
||||
def isAnonymousMVar (mctx : MetavarContext) (mvarId : MVarId) : Bool :=
|
||||
match mctx.findDecl? mvarId with
|
||||
|
||||
@@ -622,6 +622,15 @@ declaration signatures.
|
||||
/-- Debugging command: Prints the result of `Environment.dumpAsyncEnvState`. -/
|
||||
@[builtin_command_parser] def dumpAsyncEnvState := leading_parser
|
||||
"#dump_async_env_state"
|
||||
/--
|
||||
Mark a syntax kind as deprecated. When this syntax is elaborated, a warning will be emitted.
|
||||
|
||||
```
|
||||
deprecated_syntax Lean.Parser.Term.let_fun "use `have` instead" (since := "2026-03-18")
|
||||
```
|
||||
-/
|
||||
@[builtin_command_parser] def deprecatedSyntax := leading_parser
|
||||
"deprecated_syntax " >> ident >> optional (ppSpace >> strLit) >> optional (" (" >> nonReservedSymbol "since" >> " := " >> strLit >> ")")
|
||||
@[builtin_command_parser] def «init_quot» := leading_parser
|
||||
"init_quot"
|
||||
/--
|
||||
@@ -629,6 +638,27 @@ An internal bootstrapping command that reinterprets a Markdown docstring as Vers
|
||||
-/
|
||||
@[builtin_command_parser] def «docs_to_verso» := leading_parser
|
||||
"docs_to_verso " >> sepBy1 ident ", "
|
||||
/--
|
||||
`deprecated_module` marks the current module as deprecated.
|
||||
When another module imports a deprecated module, a warning is emitted during elaboration.
|
||||
|
||||
```
|
||||
deprecated_module "use NewModule instead" (since := "2026-03-19")
|
||||
```
|
||||
|
||||
The warning message is optional but recommended.
|
||||
The warning can be disabled with `set_option linter.deprecated.module false` or
|
||||
`-Dlinter.deprecated.module=false`.
|
||||
-/
|
||||
@[builtin_command_parser] def «deprecated_module» := leading_parser
|
||||
"deprecated_module" >> optional (ppSpace >> strLit) >> optional (" (" >> nonReservedSymbol "since" >> " := " >> strLit >> ")")
|
||||
|
||||
/--
|
||||
`#show_deprecated_modules` displays all modules in the current environment that have been
|
||||
marked with `deprecated_module`.
|
||||
-/
|
||||
@[builtin_command_parser] def showDeprecatedModules := leading_parser
|
||||
"#show_deprecated_modules"
|
||||
|
||||
def optionValue := nonReservedSymbol "true" <|> nonReservedSymbol "false" <|> strLit <|> numLit
|
||||
/--
|
||||
@@ -648,6 +678,12 @@ only in a single term or tactic.
|
||||
-/
|
||||
@[builtin_command_parser] def «set_option» := leading_parser
|
||||
"set_option " >> identWithPartialTrailingDot >> ppSpace >> optionValue
|
||||
/--
|
||||
`unlock_limits` disables all built-in resource limit options (currently `maxRecDepth`,
|
||||
`maxHeartbeats`, and `synthInstance.maxHeartbeats`) in the current scope by setting them to 0.
|
||||
-/
|
||||
@[builtin_command_parser] def «unlock_limits» := leading_parser
|
||||
"unlock_limits"
|
||||
def eraseAttr := leading_parser
|
||||
"-" >> rawIdent
|
||||
@[builtin_command_parser] def «attribute» := leading_parser
|
||||
|
||||
@@ -882,13 +882,19 @@ the available context).
|
||||
-/
|
||||
def identProjKind := `Lean.Parser.Term.identProj
|
||||
|
||||
@[builtin_term_parser] def dotIdent := leading_parser
|
||||
"." >> checkNoWsBefore >> rawIdent
|
||||
|
||||
def isIdent (stx : Syntax) : Bool :=
|
||||
-- antiquotations should also be allowed where an identifier is expected
|
||||
stx.isAntiquot || stx.isIdent
|
||||
|
||||
def isIdentOrDotIdent (stx : Syntax) : Bool :=
|
||||
isIdent stx || stx.isOfKind ``dotIdent
|
||||
|
||||
/-- `x.{u, ...}` explicitly specifies the universes `u, ...` of the constant `x`. -/
|
||||
@[builtin_term_parser] def explicitUniv : TrailingParser := trailing_parser
|
||||
checkStackTop isIdent "expected preceding identifier" >>
|
||||
checkStackTop isIdentOrDotIdent "expected preceding identifier" >>
|
||||
checkNoWsBefore "no space before '.{'" >> ".{" >>
|
||||
sepBy1 levelParser ", " >> "}"
|
||||
/-- `x@e` or `x@h:e` matches the pattern `e` and binds its value to the identifier `x`.
|
||||
@@ -976,9 +982,6 @@ appropriate parameter for the underlying monad's `ST` effects, then passes it to
|
||||
@[builtin_term_parser] def dynamicQuot := withoutPosition <| leading_parser
|
||||
"`(" >> ident >> "| " >> incQuotDepth (parserOfStack 1) >> ")"
|
||||
|
||||
@[builtin_term_parser] def dotIdent := leading_parser
|
||||
"." >> checkNoWsBefore >> rawIdent
|
||||
|
||||
/--
|
||||
Implementation of the `show_term` term elaborator.
|
||||
-/
|
||||
|
||||
@@ -469,6 +469,7 @@ def seq : FirstTokens → FirstTokens → FirstTokens
|
||||
| epsilon, tks => tks
|
||||
| optTokens s₁, optTokens s₂ => optTokens (s₁ ++ s₂)
|
||||
| optTokens s₁, tokens s₂ => tokens (s₁ ++ s₂)
|
||||
| optTokens _, unknown => unknown
|
||||
| tks, _ => tks
|
||||
|
||||
def toOptional : FirstTokens → FirstTokens
|
||||
|
||||
@@ -70,6 +70,9 @@ def ppExprLegacy (env : Environment) (mctx : MetavarContext) (lctx : LocalContex
|
||||
{ fileName := "<PrettyPrinter>", fileMap := default }
|
||||
{ env := env }
|
||||
|
||||
def ppLevel (l : Level) : MetaM Format := do
|
||||
ppCategory `level (← delabLevel l (prec := 0))
|
||||
|
||||
def ppTactic (stx : TSyntax `tactic) : CoreM Format := ppCategory `tactic stx
|
||||
|
||||
def ppCommand (stx : Syntax.Command) : CoreM Format := ppCategory `command stx
|
||||
@@ -110,7 +113,7 @@ builtin_initialize
|
||||
ppExprWithInfos := fun ctx e => ctx.runMetaM <| withoutContext <| ppExprWithInfos e
|
||||
ppConstNameWithInfos := fun ctx n => ctx.runMetaM <| withoutContext <| ppConstNameWithInfos n
|
||||
ppTerm := fun ctx stx => ctx.runCoreM <| withoutContext <| ppTerm stx
|
||||
ppLevel := fun ctx l => return l.format (mvars := getPPMVarsLevels ctx.opts)
|
||||
ppLevel := fun ctx l => ctx.runMetaM <| withoutContext <| ppLevel l
|
||||
ppGoal := fun ctx mvarId => ctx.runMetaM <| withoutContext <| Meta.ppGoal mvarId
|
||||
}
|
||||
|
||||
|
||||
@@ -450,6 +450,10 @@ partial def delab : Delab := do
|
||||
else
|
||||
return stx
|
||||
|
||||
def delabLevel (l : Level) (prec : Nat) : DelabM Syntax.Level := do
|
||||
let mvars ← getPPOption getPPMVarsLevels
|
||||
return Level.quote l prec (mvars := mvars) (lIndex? := (← getMCtx).findLevelIndex?)
|
||||
|
||||
/--
|
||||
Registers an unexpander for applications of a given constant.
|
||||
|
||||
@@ -477,7 +481,11 @@ unsafe builtin_initialize appUnexpanderAttribute : KeyedDeclsAttribute Unexpande
|
||||
end Delaborator
|
||||
|
||||
open SubExpr (Pos PosMap)
|
||||
open Delaborator (OptionsPerPos topDownAnalyze DelabM)
|
||||
open Delaborator (OptionsPerPos topDownAnalyze DelabM getPPOption)
|
||||
|
||||
def delabLevel (l : Level) (prec : Nat) : MetaM Syntax.Level := do
|
||||
let mvars := getPPMVarsLevels (← getOptions)
|
||||
return Level.quote l prec (mvars := mvars) (lIndex? := (← getMCtx).findLevelIndex?)
|
||||
|
||||
def delabCore (e : Expr) (optionsPerPos : OptionsPerPos := {}) (delab : DelabM α) :
|
||||
MetaM (α × PosMap Elab.Info) := do
|
||||
|
||||
@@ -91,10 +91,9 @@ def delabSort : Delab := do
|
||||
| Level.zero => `(Prop)
|
||||
| Level.succ .zero => `(Type)
|
||||
| _ =>
|
||||
let mvars ← getPPOption getPPMVarsLevels
|
||||
match l.dec with
|
||||
| some l' => `(Type $(Level.quote l' (prec := max_prec) (mvars := mvars)))
|
||||
| none => `(Sort $(Level.quote l (prec := max_prec) (mvars := mvars)))
|
||||
| some l' => `(Type $(← delabLevel l' (prec := max_prec)))
|
||||
| none => `(Sort $(← delabLevel l (prec := max_prec)))
|
||||
|
||||
/--
|
||||
Delaborator for `const` expressions.
|
||||
@@ -131,8 +130,8 @@ def delabConst : Delab := do
|
||||
|
||||
let stx ←
|
||||
if !ls.isEmpty && (← getPPOption getPPUniverses) then
|
||||
let mvars ← getPPOption getPPMVarsLevels
|
||||
`($(mkIdent c).{$[$(ls.toArray.map (Level.quote · (prec := 0) (mvars := mvars)))],*})
|
||||
let ls' ← ls.toArray.mapM fun l => delabLevel l (prec := 0)
|
||||
`($(mkIdent c).{$ls',*})
|
||||
else
|
||||
pure <| mkIdent c
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ structure LeanSemanticToken where
|
||||
stx : Syntax
|
||||
/-- Type of the semantic token. -/
|
||||
type : SemanticTokenType
|
||||
/-- In case of overlap, higher-priority tokens will take precendence -/
|
||||
/-- In case of overlap, higher-priority tokens will take precedence -/
|
||||
priority : Nat := 5
|
||||
|
||||
/-- Semantic token information with absolute LSP positions. -/
|
||||
@@ -57,7 +57,7 @@ structure AbsoluteLspSemanticToken where
|
||||
tailPos : Lsp.Position
|
||||
/-- Start position of the semantic token. -/
|
||||
type : SemanticTokenType
|
||||
/-- In case of overlap, higher-priority tokens will take precendence -/
|
||||
/-- In case of overlap, higher-priority tokens will take precedence -/
|
||||
priority : Nat := 5
|
||||
deriving BEq, Hashable, FromJson, ToJson
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ structure PPFns where
|
||||
ppExprWithInfos : PPContext → Expr → IO FormatWithInfos
|
||||
ppConstNameWithInfos : PPContext → Name → IO FormatWithInfos
|
||||
ppTerm : PPContext → Term → IO Format
|
||||
ppLevel : PPContext → Level → BaseIO Format
|
||||
ppLevel : PPContext → Level → IO Format
|
||||
ppGoal : PPContext → MVarId → IO Format
|
||||
deriving Inhabited
|
||||
|
||||
@@ -67,7 +67,7 @@ builtin_initialize ppFnsRef : IO.Ref PPFns ←
|
||||
ppExprWithInfos := fun _ e => return format (toString e)
|
||||
ppConstNameWithInfos := fun _ n => return format n
|
||||
ppTerm := fun ctx stx => return formatRawTerm ctx stx
|
||||
ppLevel := fun _ l => return format l
|
||||
ppLevel := fun ctx l => return l.format true ctx.mctx.findLevelIndex?
|
||||
ppGoal := fun _ mvarId => return formatRawGoal mvarId
|
||||
}
|
||||
|
||||
@@ -108,8 +108,14 @@ def ppTerm (ctx : PPContext) (stx : Term) : BaseIO Format := do
|
||||
else
|
||||
pure f!"failed to pretty print term (use 'set_option pp.rawOnError true' for raw representation)"
|
||||
|
||||
def ppLevel (ctx : PPContext) (l : Level) : BaseIO Format :=
|
||||
ppExt.getState ctx.env |>.ppLevel ctx l
|
||||
def ppLevel (ctx : PPContext) (l : Level) : BaseIO Format := do
|
||||
match (← ppExt.getState ctx.env |>.ppLevel ctx l |>.toBaseIO) with
|
||||
| .ok fmt => return fmt
|
||||
| .error ex =>
|
||||
if pp.rawOnError.get ctx.opts then
|
||||
pure f!"[Error pretty printing level: {ex}. Falling back to raw printer.]{Format.line}{l.format true ctx.mctx.findLevelIndex?}"
|
||||
else
|
||||
pure f!"failed to pretty print level (use 'set_option pp.rawOnError true' for raw representation)"
|
||||
|
||||
def ppGoal (ctx : PPContext) (mvarId : MVarId) : BaseIO Format := do
|
||||
match (← ppExt.getState ctx.env |>.ppGoal ctx mvarId |>.toBaseIO) with
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Lean
|
||||
|
||||
register_builtin_option maxRecDepth : Nat := {
|
||||
defValue := defaultMaxRecDepth
|
||||
descr := "maximum recursion depth for many Lean procedures"
|
||||
descr := "maximum recursion depth for many Lean procedures, 0 means no limit"
|
||||
}
|
||||
|
||||
end Lean
|
||||
|
||||
@@ -183,7 +183,8 @@ public theorem toInt?_repr (a : Int) : a.repr.toInt? = some a := by
|
||||
rw [repr_eq_if]
|
||||
split <;> (simp; omega)
|
||||
|
||||
public theorem isInt?_repr (a : Int) : a.repr.isInt = true := by
|
||||
@[simp]
|
||||
public theorem isInt_repr (a : Int) : a.repr.isInt = true := by
|
||||
simp [← String.isSome_toInt?]
|
||||
|
||||
public theorem repr_injective {a b : Int} (h : Int.repr a = Int.repr b) : a = b := by
|
||||
|
||||
@@ -14,6 +14,7 @@ public import Std.Internal.Http.Data.Status
|
||||
public import Std.Internal.Http.Data.Chunk
|
||||
public import Std.Internal.Http.Data.Headers
|
||||
public import Std.Internal.Http.Data.URI
|
||||
public import Std.Internal.Http.Data.Body
|
||||
|
||||
/-!
|
||||
# HTTP Data Types
|
||||
|
||||
24
src/Std/Internal/Http/Data/Body.lean
Normal file
24
src/Std/Internal/Http/Data/Body.lean
Normal file
@@ -0,0 +1,24 @@
|
||||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Sofia Rodrigues
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Std.Internal.Http.Data.Body.Basic
|
||||
public import Std.Internal.Http.Data.Body.Length
|
||||
public import Std.Internal.Http.Data.Body.Any
|
||||
public import Std.Internal.Http.Data.Body.Stream
|
||||
public import Std.Internal.Http.Data.Body.Empty
|
||||
public import Std.Internal.Http.Data.Body.Full
|
||||
|
||||
public section
|
||||
|
||||
/-!
|
||||
# Body
|
||||
|
||||
This module re-exports all HTTP body types: `Body.Empty`, `Body.Full`, `Body.Stream`,
|
||||
`Body.Any`, and `Body.Length`, along with the `Http.Body` typeclass and conversion
|
||||
utilities (`ToByteArray`, `FromByteArray`).
|
||||
-/
|
||||
83
src/Std/Internal/Http/Data/Body/Any.lean
Normal file
83
src/Std/Internal/Http/Data/Body/Any.lean
Normal file
@@ -0,0 +1,83 @@
|
||||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Sofia Rodrigues
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Std.Internal.Http.Data.Body.Basic
|
||||
|
||||
public section
|
||||
|
||||
/-!
|
||||
# Body.Any
|
||||
|
||||
A type-erased body backed by closures. Implements `Http.Body` and can be constructed from any
|
||||
type that also implements `Http.Body`. Used as the default handler response body type.
|
||||
-/
|
||||
|
||||
namespace Std.Http.Body
|
||||
open Std Internal IO Async
|
||||
|
||||
set_option linter.all true
|
||||
|
||||
/--
|
||||
A type-erased body handle. Operations are stored as closures, making it open to any body type
|
||||
that implements `Http.Body`.
|
||||
-/
|
||||
structure Any where
|
||||
|
||||
/--
|
||||
Receives the next body chunk. Returns `none` at end-of-stream.
|
||||
-/
|
||||
recv : Async (Option Chunk)
|
||||
|
||||
/--
|
||||
Closes the body stream.
|
||||
-/
|
||||
close : Async Unit
|
||||
|
||||
/--
|
||||
Returns `true` when the body stream is closed.
|
||||
-/
|
||||
isClosed : Async Bool
|
||||
|
||||
/--
|
||||
Selector that resolves when a chunk is available or EOF is reached.
|
||||
-/
|
||||
recvSelector : Selector (Option Chunk)
|
||||
|
||||
/--
|
||||
Returns the declared size.
|
||||
-/
|
||||
getKnownSize : Async (Option Body.Length)
|
||||
|
||||
/--
|
||||
Sets the size of the body.
|
||||
-/
|
||||
setKnownSize : Option Body.Length → Async Unit
|
||||
namespace Any
|
||||
|
||||
/--
|
||||
Erases a body of any `Http.Body` instance into a `Body.Any`.
|
||||
-/
|
||||
def ofBody [Http.Body α] (body : α) : Any where
|
||||
recv := Http.Body.recv body
|
||||
close := Http.Body.close body
|
||||
isClosed := Http.Body.isClosed body
|
||||
recvSelector := Http.Body.recvSelector body
|
||||
getKnownSize := Http.Body.getKnownSize body
|
||||
setKnownSize := Http.Body.setKnownSize body
|
||||
|
||||
end Any
|
||||
|
||||
instance : Http.Body Any where
|
||||
recv := Any.recv
|
||||
close := Any.close
|
||||
isClosed := Any.isClosed
|
||||
recvSelector := Any.recvSelector
|
||||
getKnownSize := Any.getKnownSize
|
||||
setKnownSize := Any.setKnownSize
|
||||
|
||||
end Std.Http.Body
|
||||
102
src/Std/Internal/Http/Data/Body/Basic.lean
Normal file
102
src/Std/Internal/Http/Data/Body/Basic.lean
Normal file
@@ -0,0 +1,102 @@
|
||||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Sofia Rodrigues
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Std.Internal.Async
|
||||
public import Std.Internal.Async.ContextAsync
|
||||
public import Std.Internal.Http.Data.Chunk
|
||||
public import Std.Internal.Http.Data.Headers
|
||||
public import Std.Internal.Http.Data.Body.Length
|
||||
|
||||
public section
|
||||
|
||||
/-!
|
||||
# Body.Basic
|
||||
|
||||
This module defines the `Body` typeclass for HTTP body streams, and shared conversion types
|
||||
`ToByteArray` and `FromByteArray` used for encoding and decoding body content.
|
||||
-/
|
||||
|
||||
namespace Std.Http
|
||||
open Std Internal IO Async
|
||||
|
||||
set_option linter.all true
|
||||
|
||||
/--
|
||||
Typeclass for values that can be read as HTTP body streams.
|
||||
-/
|
||||
class Body (α : Type) where
|
||||
/--
|
||||
Receives the next body chunk. Returns `none` at end-of-stream.
|
||||
-/
|
||||
recv : α → Async (Option Chunk)
|
||||
|
||||
/--
|
||||
Closes the body stream.
|
||||
-/
|
||||
close : α → Async Unit
|
||||
|
||||
/--
|
||||
Returns `true` when the body stream is closed.
|
||||
-/
|
||||
isClosed : α → Async Bool
|
||||
|
||||
/--
|
||||
Selector that resolves when a chunk is available or EOF is reached.
|
||||
-/
|
||||
recvSelector : α → Selector (Option Chunk)
|
||||
|
||||
/--
|
||||
Gets the declared size of the body.
|
||||
-/
|
||||
getKnownSize : α → Async (Option Body.Length)
|
||||
|
||||
/--
|
||||
Sets the declared size of a body.
|
||||
-/
|
||||
setKnownSize : α → Option Body.Length → Async Unit
|
||||
end Std.Http
|
||||
|
||||
namespace Std.Http.Body
|
||||
|
||||
/--
|
||||
Typeclass for types that can be converted to a `ByteArray`.
|
||||
-/
|
||||
class ToByteArray (α : Type) where
|
||||
|
||||
/--
|
||||
Transforms into a `ByteArray`.
|
||||
-/
|
||||
toByteArray : α → ByteArray
|
||||
|
||||
instance : ToByteArray ByteArray where
|
||||
toByteArray := id
|
||||
|
||||
instance : ToByteArray String where
|
||||
toByteArray := String.toUTF8
|
||||
|
||||
/--
|
||||
Typeclass for types that can be decoded from a `ByteArray`. The conversion may fail with an error
|
||||
message if the bytes are not valid for the target type.
|
||||
-/
|
||||
class FromByteArray (α : Type) where
|
||||
|
||||
/--
|
||||
Attempts to decode a `ByteArray` into the target type, returning an error message on failure.
|
||||
-/
|
||||
fromByteArray : ByteArray → Except String α
|
||||
|
||||
instance : FromByteArray ByteArray where
|
||||
fromByteArray := .ok
|
||||
|
||||
instance : FromByteArray String where
|
||||
fromByteArray bs :=
|
||||
match String.fromUTF8? bs with
|
||||
| some s => .ok s
|
||||
| none => .error "invalid UTF-8 encoding"
|
||||
|
||||
end Std.Http.Body
|
||||
116
src/Std/Internal/Http/Data/Body/Empty.lean
Normal file
116
src/Std/Internal/Http/Data/Body/Empty.lean
Normal file
@@ -0,0 +1,116 @@
|
||||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Sofia Rodrigues
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Std.Internal.Http.Data.Request
|
||||
public import Std.Internal.Http.Data.Response
|
||||
public import Std.Internal.Http.Data.Body.Any
|
||||
|
||||
public section
|
||||
|
||||
/-!
|
||||
# Body.Empty
|
||||
|
||||
Represents an always-empty, already-closed body handle.
|
||||
-/
|
||||
|
||||
namespace Std.Http.Body
|
||||
open Std Internal IO Async
|
||||
|
||||
set_option linter.all true
|
||||
|
||||
/--
|
||||
An empty body handle.
|
||||
-/
|
||||
structure Empty where
|
||||
deriving Inhabited, BEq
|
||||
|
||||
namespace Empty
|
||||
|
||||
/--
|
||||
Receives from an empty body, always returning end-of-stream.
|
||||
-/
|
||||
@[inline]
|
||||
def recv (_ : Empty) : Async (Option Chunk) :=
|
||||
pure none
|
||||
|
||||
/--
|
||||
Closes an empty body (no-op).
|
||||
-/
|
||||
@[inline]
|
||||
def close (_ : Empty) : Async Unit :=
|
||||
pure ()
|
||||
|
||||
/--
|
||||
Empty bodies are always closed for reading.
|
||||
-/
|
||||
@[inline]
|
||||
def isClosed (_ : Empty) : Async Bool :=
|
||||
pure true
|
||||
|
||||
/--
|
||||
Selector that immediately resolves with end-of-stream for an empty body.
|
||||
-/
|
||||
@[inline]
|
||||
def recvSelector (_ : Empty) : Selector (Option Chunk) where
|
||||
tryFn := pure (some none)
|
||||
registerFn waiter := do
|
||||
let lose := pure ()
|
||||
let win promise := do
|
||||
promise.resolve (.ok none)
|
||||
waiter.race lose win
|
||||
unregisterFn := pure ()
|
||||
|
||||
end Empty
|
||||
|
||||
instance : Http.Body Empty where
|
||||
recv := Empty.recv
|
||||
close := Empty.close
|
||||
isClosed := Empty.isClosed
|
||||
recvSelector := Empty.recvSelector
|
||||
getKnownSize _ := pure (some <| .fixed 0)
|
||||
setKnownSize _ _ := pure ()
|
||||
|
||||
|
||||
instance : Coe Empty Any := ⟨Any.ofBody⟩
|
||||
|
||||
instance : Coe (Response Empty) (Response Any) where
|
||||
coe f := { f with }
|
||||
|
||||
instance : Coe (ContextAsync (Response Empty)) (ContextAsync (Response Any)) where
|
||||
coe action := do
|
||||
let response ← action
|
||||
pure (response : Response Any)
|
||||
|
||||
instance : Coe (Async (Response Empty)) (ContextAsync (Response Any)) where
|
||||
coe action := do
|
||||
let response ← action
|
||||
pure (response : Response Any)
|
||||
|
||||
end Body
|
||||
|
||||
namespace Request.Builder
|
||||
open Internal.IO.Async
|
||||
|
||||
/--
|
||||
Builds a request with no body.
|
||||
-/
|
||||
def empty (builder : Builder) : Async (Request Body.Empty) :=
|
||||
pure <| builder.body {}
|
||||
|
||||
end Request.Builder
|
||||
|
||||
namespace Response.Builder
|
||||
open Internal.IO.Async
|
||||
|
||||
/--
|
||||
Builds a response with no body.
|
||||
-/
|
||||
def empty (builder : Builder) : Async (Response Body.Empty) :=
|
||||
pure <| builder.body {}
|
||||
|
||||
end Response.Builder
|
||||
232
src/Std/Internal/Http/Data/Body/Full.lean
Normal file
232
src/Std/Internal/Http/Data/Body/Full.lean
Normal file
@@ -0,0 +1,232 @@
|
||||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Sofia Rodrigues
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Std.Sync
|
||||
public import Std.Internal.Http.Data.Request
|
||||
public import Std.Internal.Http.Data.Response
|
||||
public import Std.Internal.Http.Data.Body.Any
|
||||
public import Init.Data.ByteArray
|
||||
|
||||
public section
|
||||
|
||||
/-!
|
||||
# Body.Full
|
||||
|
||||
A body backed by a fixed `ByteArray` held in a `Mutex`.
|
||||
|
||||
The byte array is consumed at most once: the first call to `recv` atomically takes the data
|
||||
and returns it as a single chunk; subsequent calls return `none` (end-of-stream).
|
||||
Closing the body discards any unconsumed data.
|
||||
-/
|
||||
|
||||
namespace Std.Http.Body
|
||||
open Std Internal IO Async
|
||||
|
||||
set_option linter.all true
|
||||
|
||||
/--
|
||||
A body backed by a fixed, mutex-protected `ByteArray`.
|
||||
|
||||
The data is consumed on the first read. Once consumed (or explicitly closed), the body
|
||||
behaves as a closed, empty channel.
|
||||
-/
|
||||
structure Full where
|
||||
private mk ::
|
||||
private state : Mutex (Option ByteArray)
|
||||
deriving Nonempty
|
||||
|
||||
namespace Full
|
||||
|
||||
private def takeChunk : AtomicT (Option ByteArray) Async (Option Chunk) := do
|
||||
match ← get with
|
||||
| none =>
|
||||
pure none
|
||||
| some data =>
|
||||
set (none : Option ByteArray)
|
||||
if data.isEmpty then
|
||||
pure none
|
||||
else
|
||||
pure (some (Chunk.ofByteArray data))
|
||||
|
||||
/--
|
||||
Creates a `Full` body from a `ByteArray`.
|
||||
-/
|
||||
def ofByteArray (data : ByteArray) : Async Full := do
|
||||
let state ← Mutex.new (some data)
|
||||
return { state }
|
||||
|
||||
/--
|
||||
Creates a `Full` body from a `String`.
|
||||
-/
|
||||
def ofString (data : String) : Async Full := do
|
||||
let state ← Mutex.new (some data.toUTF8)
|
||||
return { state }
|
||||
|
||||
/--
|
||||
Receives the body data. Returns the full byte array on the first call as a single chunk,
|
||||
then `none` on all subsequent calls.
|
||||
-/
|
||||
def recv (full : Full) : Async (Option Chunk) :=
|
||||
full.state.atomically do
|
||||
takeChunk
|
||||
|
||||
/--
|
||||
Closes the body, discarding any unconsumed data.
|
||||
-/
|
||||
def close (full : Full) : Async Unit :=
|
||||
full.state.atomically do
|
||||
set (none : Option ByteArray)
|
||||
|
||||
/--
|
||||
Returns `true` when the data has been consumed or the body has been closed.
|
||||
-/
|
||||
def isClosed (full : Full) : Async Bool :=
|
||||
full.state.atomically do
|
||||
return (← get).isNone
|
||||
|
||||
/--
|
||||
Returns the known size of the remaining data.
|
||||
Returns `some (.fixed n)` with the current byte count, or `some (.fixed 0)` if the body has
|
||||
already been consumed or closed.
|
||||
-/
|
||||
def getKnownSize (full : Full) : Async (Option Body.Length) :=
|
||||
full.state.atomically do
|
||||
match ← get with
|
||||
| none => pure (some (.fixed 0))
|
||||
| some data => pure (some (.fixed data.size))
|
||||
|
||||
/--
|
||||
Selector that immediately resolves to the remaining chunk (or EOF).
|
||||
-/
|
||||
def recvSelector (full : Full) : Selector (Option Chunk) where
|
||||
tryFn := do
|
||||
let chunk ← full.state.atomically do
|
||||
takeChunk
|
||||
pure (some chunk)
|
||||
|
||||
registerFn waiter := do
|
||||
full.state.atomically do
|
||||
let lose := pure ()
|
||||
|
||||
let win promise := do
|
||||
let chunk ← takeChunk
|
||||
promise.resolve (.ok chunk)
|
||||
|
||||
waiter.race lose win
|
||||
|
||||
unregisterFn := pure ()
|
||||
|
||||
end Full
|
||||
|
||||
instance : Http.Body Full where
|
||||
recv := Full.recv
|
||||
close := Full.close
|
||||
isClosed := Full.isClosed
|
||||
recvSelector := Full.recvSelector
|
||||
getKnownSize := Full.getKnownSize
|
||||
setKnownSize _ _ := pure ()
|
||||
|
||||
instance : Coe Full Any := ⟨Any.ofBody⟩
|
||||
|
||||
instance : Coe (Response Full) (Response Any) where
|
||||
coe f := { f with }
|
||||
|
||||
instance : Coe (ContextAsync (Response Full)) (ContextAsync (Response Any)) where
|
||||
coe action := do
|
||||
let response ← action
|
||||
pure (response : Response Any)
|
||||
|
||||
instance : Coe (Async (Response Full)) (ContextAsync (Response Any)) where
|
||||
coe action := do
|
||||
let response ← action
|
||||
pure (response : Response Any)
|
||||
|
||||
end Body
|
||||
|
||||
namespace Request.Builder
|
||||
|
||||
open Internal.IO.Async
|
||||
|
||||
/--
|
||||
Builds a request body from raw bytes without setting any headers.
|
||||
Use `bytes` instead if you want `Content-Type: application/octet-stream` set automatically.
|
||||
-/
|
||||
def fromBytes (builder : Builder) (content : ByteArray) : Async (Request Body.Full) := do
|
||||
return builder.body (← Body.Full.ofByteArray content)
|
||||
|
||||
/--
|
||||
Builds a request with a binary body.
|
||||
Sets `Content-Type: application/octet-stream`.
|
||||
Use `fromBytes` instead if you need to set a different `Content-Type` or none at all.
|
||||
-/
|
||||
def bytes (builder : Builder) (content : ByteArray) : Async (Request Body.Full) :=
|
||||
fromBytes (builder.header Header.Name.contentType (Header.Value.ofString! "application/octet-stream")) content
|
||||
|
||||
/--
|
||||
Builds a request with a text body.
|
||||
Sets `Content-Type: text/plain; charset=utf-8`.
|
||||
-/
|
||||
def text (builder : Builder) (content : String) : Async (Request Body.Full) :=
|
||||
fromBytes (builder.header Header.Name.contentType (Header.Value.ofString! "text/plain; charset=utf-8")) content.toUTF8
|
||||
|
||||
/--
|
||||
Builds a request with a JSON body.
|
||||
Sets `Content-Type: application/json`.
|
||||
-/
|
||||
def json (builder : Builder) (content : String) : Async (Request Body.Full) :=
|
||||
fromBytes (builder.header Header.Name.contentType (Header.Value.ofString! "application/json")) content.toUTF8
|
||||
|
||||
/--
|
||||
Builds a request with an HTML body.
|
||||
Sets `Content-Type: text/html; charset=utf-8`.
|
||||
-/
|
||||
def html (builder : Builder) (content : String) : Async (Request Body.Full) :=
|
||||
fromBytes (builder.header Header.Name.contentType (Header.Value.ofString! "text/html; charset=utf-8")) content.toUTF8
|
||||
|
||||
end Request.Builder
|
||||
|
||||
namespace Response.Builder
|
||||
open Internal.IO.Async
|
||||
|
||||
/--
|
||||
Builds a response body from raw bytes without setting any headers.
|
||||
Use `bytes` instead if you want `Content-Type: application/octet-stream` set automatically.
|
||||
-/
|
||||
def fromBytes (builder : Builder) (content : ByteArray) : Async (Response Body.Full) := do
|
||||
return builder.body (← Body.Full.ofByteArray content)
|
||||
|
||||
/--
|
||||
Builds a response with a binary body.
|
||||
Sets `Content-Type: application/octet-stream`.
|
||||
Use `fromBytes` instead if you need to set a different `Content-Type` or none at all.
|
||||
-/
|
||||
def bytes (builder : Builder) (content : ByteArray) : Async (Response Body.Full) :=
|
||||
fromBytes (builder.header Header.Name.contentType (Header.Value.ofString! "application/octet-stream")) content
|
||||
|
||||
/--
|
||||
Builds a response with a text body.
|
||||
Sets `Content-Type: text/plain; charset=utf-8`.
|
||||
-/
|
||||
def text (builder : Builder) (content : String) : Async (Response Body.Full) :=
|
||||
fromBytes (builder.header Header.Name.contentType (Header.Value.ofString! "text/plain; charset=utf-8")) content.toUTF8
|
||||
|
||||
/--
|
||||
Builds a response with a JSON body.
|
||||
Sets `Content-Type: application/json`.
|
||||
-/
|
||||
def json (builder : Builder) (content : String) : Async (Response Body.Full) :=
|
||||
fromBytes (builder.header Header.Name.contentType (Header.Value.ofString! "application/json")) content.toUTF8
|
||||
|
||||
/--
|
||||
Builds a response with an HTML body.
|
||||
Sets `Content-Type: text/html; charset=utf-8`.
|
||||
-/
|
||||
def html (builder : Builder) (content : String) : Async (Response Body.Full) :=
|
||||
fromBytes (builder.header Header.Name.contentType (Header.Value.ofString! "text/html; charset=utf-8")) content.toUTF8
|
||||
|
||||
end Response.Builder
|
||||
60
src/Std/Internal/Http/Data/Body/Length.lean
Normal file
60
src/Std/Internal/Http/Data/Body/Length.lean
Normal file
@@ -0,0 +1,60 @@
|
||||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Sofia Rodrigues
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Data.Repr
|
||||
|
||||
public section
|
||||
|
||||
/-!
|
||||
# Body.Length
|
||||
|
||||
This module defines the `Length` type, that represents the Content-Length or Transfer-Encoding
|
||||
of an HTTP request or response.
|
||||
-/
|
||||
|
||||
namespace Std.Http.Body
|
||||
|
||||
set_option linter.all true
|
||||
|
||||
/--
|
||||
Size of the body of a response or request.
|
||||
-/
|
||||
inductive Length
|
||||
/--
|
||||
Indicates that the HTTP message body uses **chunked transfer encoding**.
|
||||
-/
|
||||
| chunked
|
||||
|
||||
/--
|
||||
Indicates that the HTTP message body has a **fixed, known length**, as specified by the
|
||||
`Content-Length` header.
|
||||
-/
|
||||
| fixed (n : Nat)
|
||||
deriving Repr, BEq
|
||||
|
||||
namespace Length
|
||||
|
||||
/--
|
||||
Checks if the `Length` is chunked.
|
||||
-/
|
||||
@[inline]
|
||||
def isChunked : Length → Bool
|
||||
| .chunked => true
|
||||
| _ => false
|
||||
|
||||
/--
|
||||
Checks if the `Length` is a fixed size.
|
||||
-/
|
||||
@[inline]
|
||||
def isFixed : Length → Bool
|
||||
| .fixed _ => true
|
||||
| _ => false
|
||||
|
||||
end Length
|
||||
|
||||
end Std.Http.Body
|
||||
650
src/Std/Internal/Http/Data/Body/Stream.lean
Normal file
650
src/Std/Internal/Http/Data/Body/Stream.lean
Normal file
@@ -0,0 +1,650 @@
|
||||
/-
|
||||
Copyright (c) 2025 Lean FRO, LLC. All rights reserved.
|
||||
Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Authors: Sofia Rodrigues
|
||||
-/
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Std.Sync
|
||||
public import Std.Internal.Async
|
||||
public import Std.Internal.Http.Data.Request
|
||||
public import Std.Internal.Http.Data.Response
|
||||
public import Std.Internal.Http.Data.Chunk
|
||||
public import Std.Internal.Http.Data.Body.Basic
|
||||
public import Std.Internal.Http.Data.Body.Any
|
||||
public import Init.Data.ByteArray
|
||||
|
||||
public section
|
||||
|
||||
/-!
|
||||
# Body.Stream
|
||||
|
||||
This module defines a zero-buffer rendezvous body channel (`Body.Stream`) that supports
|
||||
both sending and receiving chunks.
|
||||
|
||||
There is no queue and no capacity. A send waits for a receiver and a receive waits for a sender.
|
||||
At most one blocked producer and one blocked consumer are supported.
|
||||
-/
|
||||
|
||||
namespace Std.Http
|
||||
|
||||
namespace Body
|
||||
|
||||
open Std Internal IO Async
|
||||
|
||||
set_option linter.all true
|
||||
|
||||
namespace Channel
|
||||
|
||||
open Internal.IO.Async in
|
||||
private inductive Consumer where
|
||||
| normal (promise : IO.Promise (Option Chunk))
|
||||
| select (finished : Waiter (Option Chunk))
|
||||
|
||||
private def Consumer.resolve (c : Consumer) (x : Option Chunk) : BaseIO Bool := do
|
||||
match c with
|
||||
| .normal promise =>
|
||||
promise.resolve x
|
||||
return true
|
||||
| .select waiter =>
|
||||
let lose := return false
|
||||
let win promise := do
|
||||
promise.resolve (.ok x)
|
||||
return true
|
||||
waiter.race lose win
|
||||
|
||||
private structure Producer where
|
||||
chunk : Chunk
|
||||
|
||||
/--
|
||||
Resolved with `true` when consumed by a receiver, `false` when the channel closes.
|
||||
-/
|
||||
done : IO.Promise Bool
|
||||
|
||||
open Internal.IO.Async in
|
||||
private def resolveInterestWaiter (waiter : Waiter Bool) (x : Bool) : BaseIO Bool := do
|
||||
let lose := return false
|
||||
let win promise := do
|
||||
promise.resolve (.ok x)
|
||||
return true
|
||||
waiter.race lose win
|
||||
|
||||
private structure State where
|
||||
/--
|
||||
A single blocked producer waiting for a receiver.
|
||||
-/
|
||||
pendingProducer : Option Producer
|
||||
|
||||
/--
|
||||
A single blocked consumer waiting for a producer.
|
||||
-/
|
||||
pendingConsumer : Option Consumer
|
||||
|
||||
/--
|
||||
A waiter for `Stream.interestSelector`.
|
||||
-/
|
||||
interestWaiter : Option (Internal.IO.Async.Waiter Bool)
|
||||
|
||||
/--
|
||||
Whether the channel is closed.
|
||||
-/
|
||||
closed : Bool
|
||||
|
||||
/--
|
||||
Known size of the stream if available.
|
||||
-/
|
||||
knownSize : Option Body.Length
|
||||
|
||||
/--
|
||||
Buffered partial chunk data accumulated from `Stream.send ... (incomplete := true)`.
|
||||
These partial pieces are collapsed and emitted as a single chunk on the next complete send.
|
||||
-/
|
||||
pendingIncompleteChunk : Option Chunk := none
|
||||
deriving Nonempty
|
||||
|
||||
end Channel
|
||||
|
||||
/--
|
||||
A zero-buffer rendezvous body channel that supports both sending and receiving chunks.
|
||||
-/
|
||||
structure Stream where
|
||||
private mk ::
|
||||
private state : Mutex Channel.State
|
||||
deriving Nonempty, TypeName
|
||||
|
||||
/--
|
||||
Creates a rendezvous body stream.
|
||||
-/
|
||||
def mkStream : Async Stream := do
|
||||
let state ← Mutex.new {
|
||||
pendingProducer := none
|
||||
pendingConsumer := none
|
||||
interestWaiter := none
|
||||
closed := false
|
||||
knownSize := none
|
||||
}
|
||||
return { state }
|
||||
|
||||
namespace Channel
|
||||
|
||||
private def decreaseKnownSize (knownSize : Option Body.Length) (chunk : Chunk) : Option Body.Length :=
|
||||
match knownSize with
|
||||
| some (.fixed res) => some (Body.Length.fixed (res - chunk.data.size))
|
||||
| _ => knownSize
|
||||
|
||||
private def pruneFinishedWaiters [Monad m] [MonadLiftT (ST IO.RealWorld) m] :
|
||||
AtomicT State m Unit := do
|
||||
let st ← get
|
||||
|
||||
let pendingConsumer ←
|
||||
match st.pendingConsumer with
|
||||
| some (.select waiter) =>
|
||||
if ← waiter.checkFinished then
|
||||
pure none
|
||||
else
|
||||
pure st.pendingConsumer
|
||||
| _ =>
|
||||
pure st.pendingConsumer
|
||||
|
||||
let interestWaiter ←
|
||||
match st.interestWaiter with
|
||||
| some waiter =>
|
||||
if ← waiter.checkFinished then
|
||||
pure none
|
||||
else
|
||||
pure st.interestWaiter
|
||||
| none =>
|
||||
pure none
|
||||
|
||||
set { st with pendingConsumer, interestWaiter }
|
||||
|
||||
private def signalInterest [Monad m] [MonadLiftT (ST IO.RealWorld) m] [MonadLiftT BaseIO m] :
|
||||
AtomicT State m Unit := do
|
||||
let st ← get
|
||||
if let some waiter := st.interestWaiter then
|
||||
discard <| resolveInterestWaiter waiter true
|
||||
set { st with interestWaiter := none }
|
||||
|
||||
private def recvReady' [Monad m] [MonadLiftT (ST IO.RealWorld) m] :
|
||||
AtomicT State m Bool := do
|
||||
let st ← get
|
||||
return st.pendingProducer.isSome || st.closed
|
||||
|
||||
private def hasInterest' [Monad m] [MonadLiftT (ST IO.RealWorld) m] :
|
||||
AtomicT State m Bool := do
|
||||
let st ← get
|
||||
return st.pendingConsumer.isSome
|
||||
|
||||
private def tryRecv' [Monad m] [MonadLiftT (ST IO.RealWorld) m] [MonadLiftT BaseIO m] :
|
||||
AtomicT State m (Option Chunk) := do
|
||||
let st ← get
|
||||
if let some producer := st.pendingProducer then
|
||||
set {
|
||||
st with
|
||||
pendingProducer := none
|
||||
knownSize := decreaseKnownSize st.knownSize producer.chunk
|
||||
}
|
||||
discard <| producer.done.resolve true
|
||||
return some producer.chunk
|
||||
else
|
||||
return none
|
||||
|
||||
private def close' [Monad m] [MonadLiftT (ST IO.RealWorld) m] [MonadLiftT BaseIO m] :
|
||||
AtomicT State m Unit := do
|
||||
let st ← get
|
||||
if st.closed then
|
||||
return ()
|
||||
|
||||
if let some consumer := st.pendingConsumer then
|
||||
discard <| consumer.resolve none
|
||||
|
||||
if let some waiter := st.interestWaiter then
|
||||
discard <| resolveInterestWaiter waiter false
|
||||
|
||||
if let some producer := st.pendingProducer then
|
||||
discard <| producer.done.resolve false
|
||||
|
||||
set {
|
||||
st with
|
||||
pendingProducer := none
|
||||
pendingConsumer := none
|
||||
interestWaiter := none
|
||||
pendingIncompleteChunk := none
|
||||
closed := true
|
||||
}
|
||||
|
||||
end Channel
|
||||
|
||||
namespace Stream
|
||||
|
||||
/--
|
||||
Attempts to receive a chunk from the channel without blocking.
|
||||
Returns `some chunk` only when a producer is already waiting.
|
||||
-/
|
||||
def tryRecv (stream : Stream) : Async (Option Chunk) :=
|
||||
stream.state.atomically do
|
||||
Channel.pruneFinishedWaiters
|
||||
Channel.tryRecv'
|
||||
|
||||
private def recv' (stream : Stream) : BaseIO (AsyncTask (Option Chunk)) := do
|
||||
stream.state.atomically do
|
||||
Channel.pruneFinishedWaiters
|
||||
|
||||
if let some chunk ← Channel.tryRecv' then
|
||||
return AsyncTask.pure (some chunk)
|
||||
|
||||
let st ← get
|
||||
if st.closed then
|
||||
return AsyncTask.pure none
|
||||
|
||||
if st.pendingConsumer.isSome then
|
||||
return Task.pure (.error (IO.Error.userError "only one blocked consumer is allowed"))
|
||||
|
||||
let promise ← IO.Promise.new
|
||||
set { st with pendingConsumer := some (.normal promise) }
|
||||
Channel.signalInterest
|
||||
return promise.result?.map (sync := true) fun
|
||||
| none => .error (IO.Error.userError "the promise linked to the consumer was dropped")
|
||||
| some res => .ok res
|
||||
|
||||
/--
|
||||
Receives a chunk from the channel. Blocks until a producer sends one.
|
||||
Returns `none` if the channel is closed and no producer is waiting.
|
||||
-/
|
||||
def recv (stream : Stream) : Async (Option Chunk) := do
|
||||
Async.ofAsyncTask (← recv' stream)
|
||||
|
||||
/--
|
||||
Closes the channel.
|
||||
-/
|
||||
def close (stream : Stream) : Async Unit :=
|
||||
stream.state.atomically do
|
||||
Channel.close'
|
||||
|
||||
/--
|
||||
Checks whether the channel is closed.
|
||||
-/
|
||||
@[always_inline, inline]
|
||||
def isClosed (stream : Stream) : Async Bool :=
|
||||
stream.state.atomically do
|
||||
return (← get).closed
|
||||
|
||||
/--
|
||||
Gets the known size if available.
|
||||
-/
|
||||
@[always_inline, inline]
|
||||
def getKnownSize (stream : Stream) : Async (Option Body.Length) :=
|
||||
stream.state.atomically do
|
||||
return (← get).knownSize
|
||||
|
||||
/--
|
||||
Sets known size metadata.
|
||||
-/
|
||||
@[always_inline, inline]
|
||||
def setKnownSize (stream : Stream) (size : Option Body.Length) : Async Unit :=
|
||||
stream.state.atomically do
|
||||
modify fun st => { st with knownSize := size }
|
||||
|
||||
open Internal.IO.Async in
|
||||
|
||||
/--
|
||||
Creates a selector that resolves when a producer is waiting (or the channel closes).
|
||||
-/
|
||||
def recvSelector (stream : Stream) : Selector (Option Chunk) where
|
||||
tryFn := do
|
||||
stream.state.atomically do
|
||||
Channel.pruneFinishedWaiters
|
||||
if ← Channel.recvReady' then
|
||||
return some (← Channel.tryRecv')
|
||||
else
|
||||
return none
|
||||
|
||||
registerFn waiter := do
|
||||
stream.state.atomically do
|
||||
Channel.pruneFinishedWaiters
|
||||
if ← Channel.recvReady' then
|
||||
let lose := return ()
|
||||
let win promise := do
|
||||
promise.resolve (.ok (← Channel.tryRecv'))
|
||||
waiter.race lose win
|
||||
else
|
||||
let st ← get
|
||||
if st.pendingConsumer.isSome then
|
||||
throw (.userError "only one blocked consumer is allowed")
|
||||
|
||||
set { st with pendingConsumer := some (.select waiter) }
|
||||
Channel.signalInterest
|
||||
|
||||
unregisterFn := do
|
||||
stream.state.atomically do
|
||||
Channel.pruneFinishedWaiters
|
||||
|
||||
/--
|
||||
Iterates over chunks until the channel closes.
|
||||
-/
|
||||
@[inline]
|
||||
protected partial def forIn
|
||||
{β : Type} (stream : Stream) (acc : β)
|
||||
(step : Chunk → β → Async (ForInStep β)) : Async β := do
|
||||
|
||||
let rec @[specialize] loop (stream : Stream) (acc : β) : Async β := do
|
||||
if let some chunk ← stream.recv then
|
||||
match ← step chunk acc with
|
||||
| .done res => return res
|
||||
| .yield res => loop stream res
|
||||
else
|
||||
return acc
|
||||
|
||||
loop stream acc
|
||||
|
||||
/--
|
||||
Context-aware iteration over chunks until the channel closes.
|
||||
-/
|
||||
@[inline]
|
||||
protected partial def forIn'
|
||||
{β : Type} (stream : Stream) (acc : β)
|
||||
(step : Chunk → β → ContextAsync (ForInStep β)) : ContextAsync β := do
|
||||
|
||||
let rec @[specialize] loop (stream : Stream) (acc : β) : ContextAsync β := do
|
||||
let data ← Selectable.one #[
|
||||
.case stream.recvSelector pure,
|
||||
.case (← ContextAsync.doneSelector) (fun _ => pure none),
|
||||
]
|
||||
|
||||
if let some chunk := data then
|
||||
match ← step chunk acc with
|
||||
| .done res => return res
|
||||
| .yield res => loop stream res
|
||||
else
|
||||
return acc
|
||||
|
||||
loop stream acc
|
||||
|
||||
/--
|
||||
Abstracts over how the next chunk is received, allowing `readAll` to work in both `Async`
|
||||
(no cancellation) and `ContextAsync` (races with cancellation via `doneSelector`).
|
||||
-/
|
||||
class NextChunk (m : Type → Type) where
|
||||
/--
|
||||
Receives the next chunk, stopping at EOF or (in `ContextAsync`) when the context is cancelled.
|
||||
-/
|
||||
nextChunk : Stream → m (Option Chunk)
|
||||
|
||||
instance : NextChunk Async where
|
||||
nextChunk := Stream.recv
|
||||
|
||||
instance : NextChunk ContextAsync where
|
||||
nextChunk stream := do
|
||||
Selectable.one #[
|
||||
.case stream.recvSelector pure,
|
||||
.case (← ContextAsync.doneSelector) (fun _ => pure none),
|
||||
]
|
||||
|
||||
/--
|
||||
Reads all remaining chunks and decodes them into `α`.
|
||||
|
||||
Works in both `Async` (reads until EOF, no cancellation) and `ContextAsync` (also stops if the
|
||||
context is cancelled).
|
||||
-/
|
||||
partial def readAll
|
||||
[FromByteArray α]
|
||||
[Monad m] [MonadExceptOf IO.Error m] [NextChunk m]
|
||||
(stream : Stream)
|
||||
(maximumSize : Option UInt64 := none) :
|
||||
m α := do
|
||||
|
||||
let rec loop (result : ByteArray) : m ByteArray := do
|
||||
match ← NextChunk.nextChunk stream with
|
||||
| none => return result
|
||||
| some chunk =>
|
||||
let result := result ++ chunk.data
|
||||
if let some max := maximumSize then
|
||||
if result.size.toUInt64 > max then
|
||||
throw (.userError s!"body exceeded maximum size of {max} bytes")
|
||||
loop result
|
||||
|
||||
let result ← loop ByteArray.empty
|
||||
|
||||
match FromByteArray.fromByteArray result with
|
||||
| .ok a => return a
|
||||
| .error msg => throw (.userError msg)
|
||||
|
||||
private def collapseForSend
|
||||
(stream : Stream)
|
||||
(chunk : Chunk)
|
||||
(incomplete : Bool) : BaseIO (Except IO.Error (Option Chunk)) := do
|
||||
|
||||
stream.state.atomically do
|
||||
Channel.pruneFinishedWaiters
|
||||
let st ← get
|
||||
|
||||
if st.closed then
|
||||
return .error (.userError "channel closed")
|
||||
|
||||
let merged := match st.pendingIncompleteChunk with
|
||||
| some pending =>
|
||||
{
|
||||
data := pending.data ++ chunk.data
|
||||
extensions := if pending.extensions.isEmpty then chunk.extensions else pending.extensions
|
||||
}
|
||||
| none => chunk
|
||||
|
||||
if incomplete then
|
||||
set { st with pendingIncompleteChunk := some merged }
|
||||
return .ok none
|
||||
else
|
||||
set { st with pendingIncompleteChunk := none }
|
||||
return .ok (some merged)
|
||||
|
||||
/--
|
||||
Sends a chunk, retrying if a select-mode consumer races and loses. If no consumer is ready,
|
||||
installs the chunk as a pending producer and awaits acknowledgement from the receiver.
|
||||
-/
|
||||
private partial def send' (stream : Stream) (chunk : Chunk) : Async Unit := do
|
||||
let done ← IO.Promise.new
|
||||
let result : Except IO.Error (Option Bool) ← stream.state.atomically do
|
||||
Channel.pruneFinishedWaiters
|
||||
let st ← get
|
||||
|
||||
if st.closed then
|
||||
return .error (IO.Error.userError "channel closed")
|
||||
|
||||
if let some consumer := st.pendingConsumer then
|
||||
let success ← consumer.resolve (some chunk)
|
||||
|
||||
if success then
|
||||
set {
|
||||
st with
|
||||
pendingConsumer := none
|
||||
knownSize := Channel.decreaseKnownSize st.knownSize chunk
|
||||
}
|
||||
return .ok (some true)
|
||||
else
|
||||
set { st with pendingConsumer := none }
|
||||
return .ok (some false)
|
||||
else if st.pendingProducer.isSome then
|
||||
return .error (IO.Error.userError "only one blocked producer is allowed")
|
||||
else
|
||||
set { st with pendingProducer := some { chunk, done } }
|
||||
return .ok none
|
||||
|
||||
match result with
|
||||
| .error err =>
|
||||
throw err
|
||||
| .ok (some true) =>
|
||||
return ()
|
||||
| .ok (some false) =>
|
||||
-- The select-mode consumer raced and lost; recurse to allocate a fresh `done` promise.
|
||||
send' stream chunk
|
||||
| .ok none =>
|
||||
match ← await done.result? with
|
||||
| some true => return ()
|
||||
| _ => throw (IO.Error.userError "channel closed")
|
||||
|
||||
/--
|
||||
Sends a chunk.
|
||||
|
||||
If `incomplete := true`, the chunk is buffered and collapsed with subsequent chunks, and is not
|
||||
delivered to the receiver yet.
|
||||
If `incomplete := false`, any buffered incomplete pieces are collapsed with this chunk and the
|
||||
single merged chunk is sent.
|
||||
-/
|
||||
def send (stream : Stream) (chunk : Chunk) (incomplete : Bool := false) : Async Unit := do
|
||||
match (← collapseForSend stream chunk incomplete) with
|
||||
| .error err => throw err
|
||||
| .ok none => pure ()
|
||||
| .ok (some toSend) =>
|
||||
if toSend.data.isEmpty ∧ toSend.extensions.isEmpty then
|
||||
return ()
|
||||
|
||||
send' stream toSend
|
||||
|
||||
/--
|
||||
Returns `true` when a consumer is currently blocked waiting for data.
|
||||
-/
|
||||
def hasInterest (stream : Stream) : Async Bool :=
|
||||
stream.state.atomically do
|
||||
Channel.pruneFinishedWaiters
|
||||
Channel.hasInterest'
|
||||
|
||||
open Internal.IO.Async in
|
||||
/--
|
||||
Creates a selector that resolves when consumer interest is present.
|
||||
Returns `true` when a consumer is waiting, `false` when the channel closes first.
|
||||
-/
|
||||
def interestSelector (stream : Stream) : Selector Bool where
|
||||
tryFn := do
|
||||
stream.state.atomically do
|
||||
Channel.pruneFinishedWaiters
|
||||
let st ← get
|
||||
if st.pendingConsumer.isSome then
|
||||
return some true
|
||||
else if st.closed then
|
||||
return some false
|
||||
else
|
||||
return none
|
||||
|
||||
registerFn waiter := do
|
||||
stream.state.atomically do
|
||||
Channel.pruneFinishedWaiters
|
||||
let st ← get
|
||||
|
||||
if st.pendingConsumer.isSome then
|
||||
let lose := return ()
|
||||
let win promise := do
|
||||
promise.resolve (.ok true)
|
||||
waiter.race lose win
|
||||
else if st.closed then
|
||||
let lose := return ()
|
||||
let win promise := do
|
||||
promise.resolve (.ok false)
|
||||
waiter.race lose win
|
||||
else if st.interestWaiter.isSome then
|
||||
throw (.userError "only one blocked interest selector is allowed")
|
||||
else
|
||||
set { st with interestWaiter := some waiter }
|
||||
|
||||
unregisterFn := do
|
||||
stream.state.atomically do
|
||||
Channel.pruneFinishedWaiters
|
||||
|
||||
end Stream
|
||||
|
||||
/--
|
||||
Creates a body from a producer function.
|
||||
Returns the stream immediately and runs `gen` in a detached task.
|
||||
The channel is always closed when `gen` returns or throws.
|
||||
Errors from `gen` are not rethrown here; consumers observe end-of-stream via `recv = none`.
|
||||
-/
|
||||
def stream (gen : Stream → Async Unit) : Async Stream := do
|
||||
let s ← mkStream
|
||||
background <| do
|
||||
try
|
||||
gen s
|
||||
finally
|
||||
s.close
|
||||
return s
|
||||
|
||||
/--
|
||||
Creates a body from a fixed byte array.
|
||||
-/
|
||||
def fromBytes (content : ByteArray) : Async Stream := do
|
||||
stream fun s => do
|
||||
s.setKnownSize (some (.fixed content.size))
|
||||
if content.size > 0 then
|
||||
s.send (Chunk.ofByteArray content)
|
||||
|
||||
/--
|
||||
Creates an empty `Stream` body channel (already closed, no data).
|
||||
|
||||
Prefer `Body.Empty` when you need a concrete zero-cost type. Use this when the calling
|
||||
context requires a `Stream` specifically.
|
||||
-/
|
||||
def empty : Async Stream := do
|
||||
let s ← mkStream
|
||||
s.setKnownSize (some (.fixed 0))
|
||||
s.close
|
||||
return s
|
||||
|
||||
instance : ForIn Async Stream Chunk where
|
||||
forIn := Stream.forIn
|
||||
|
||||
instance : ForIn ContextAsync Stream Chunk where
|
||||
forIn := Stream.forIn'
|
||||
|
||||
instance : Http.Body Stream where
|
||||
recv := Stream.recv
|
||||
close := Stream.close
|
||||
isClosed := Stream.isClosed
|
||||
recvSelector := Stream.recvSelector
|
||||
getKnownSize := Stream.getKnownSize
|
||||
setKnownSize := Stream.setKnownSize
|
||||
|
||||
instance : Coe Stream Any := ⟨Any.ofBody⟩
|
||||
|
||||
instance : Coe (Response Stream) (Response Any) where
|
||||
coe f := { f with }
|
||||
|
||||
instance : Coe (ContextAsync (Response Stream)) (ContextAsync (Response Any)) where
|
||||
coe action := do
|
||||
let response ← action
|
||||
pure (response : Response Any)
|
||||
|
||||
instance : Coe (Async (Response Stream)) (ContextAsync (Response Any)) where
|
||||
coe action := do
|
||||
let response ← action
|
||||
pure (response : Response Any)
|
||||
|
||||
end Body
|
||||
|
||||
namespace Request.Builder
|
||||
|
||||
open Internal.IO.Async
|
||||
|
||||
/--
|
||||
Builds a request with a streaming body generator.
|
||||
-/
|
||||
def stream
|
||||
(builder : Builder)
|
||||
(gen : Body.Stream → Async Unit) :
|
||||
Async (Request Body.Stream) := do
|
||||
let s ← Body.stream gen
|
||||
return Request.Builder.body builder s
|
||||
|
||||
end Request.Builder
|
||||
|
||||
namespace Response.Builder
|
||||
open Internal.IO.Async
|
||||
|
||||
/--
|
||||
Builds a response with a streaming body generator.
|
||||
-/
|
||||
def stream
|
||||
(builder : Builder)
|
||||
(gen : Body.Stream → Async Unit) :
|
||||
Async (Response Body.Stream) := do
|
||||
let s ← Body.stream gen
|
||||
return Response.Builder.body builder s
|
||||
|
||||
end Response.Builder
|
||||
@@ -124,12 +124,6 @@ def new : Builder := { }
|
||||
|
||||
namespace Builder
|
||||
|
||||
/--
|
||||
Creates a new HTTP request builder with the default head
|
||||
(method: GET, version: HTTP/1.1, target: `*`).
|
||||
-/
|
||||
def empty : Builder := { }
|
||||
|
||||
/--
|
||||
Sets the HTTP method for the request being built.
|
||||
-/
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Builder
|
||||
/--
|
||||
Creates a new HTTP Response builder with default head (status: 200 OK, version: HTTP/1.1).
|
||||
-/
|
||||
def empty : Builder := { }
|
||||
def new : Builder := { }
|
||||
|
||||
/--
|
||||
Sets the HTTP status code for the response being built.
|
||||
@@ -173,66 +173,66 @@ end Builder
|
||||
Creates a new HTTP Response builder with the 200 status code.
|
||||
-/
|
||||
def ok : Builder :=
|
||||
.empty |>.status .ok
|
||||
.new |>.status .ok
|
||||
|
||||
/--
|
||||
Creates a new HTTP Response builder with the provided status.
|
||||
-/
|
||||
def withStatus (status : Status) : Builder :=
|
||||
.empty |>.status status
|
||||
.new |>.status status
|
||||
|
||||
/--
|
||||
Creates a new HTTP Response builder with the 404 status code.
|
||||
-/
|
||||
def notFound : Builder :=
|
||||
.empty |>.status .notFound
|
||||
.new |>.status .notFound
|
||||
|
||||
/--
|
||||
Creates a new HTTP Response builder with the 500 status code.
|
||||
-/
|
||||
def internalServerError : Builder :=
|
||||
.empty |>.status .internalServerError
|
||||
.new |>.status .internalServerError
|
||||
|
||||
/--
|
||||
Creates a new HTTP Response builder with the 400 status code.
|
||||
-/
|
||||
def badRequest : Builder :=
|
||||
.empty |>.status .badRequest
|
||||
.new |>.status .badRequest
|
||||
|
||||
/--
|
||||
Creates a new HTTP Response builder with the 201 status code.
|
||||
-/
|
||||
def created : Builder :=
|
||||
.empty |>.status .created
|
||||
.new |>.status .created
|
||||
|
||||
/--
|
||||
Creates a new HTTP Response builder with the 202 status code.
|
||||
-/
|
||||
def accepted : Builder :=
|
||||
.empty |>.status .accepted
|
||||
.new |>.status .accepted
|
||||
|
||||
/--
|
||||
Creates a new HTTP Response builder with the 401 status code.
|
||||
-/
|
||||
def unauthorized : Builder :=
|
||||
.empty |>.status .unauthorized
|
||||
.new |>.status .unauthorized
|
||||
|
||||
/--
|
||||
Creates a new HTTP Response builder with the 403 status code.
|
||||
-/
|
||||
def forbidden : Builder :=
|
||||
.empty |>.status .forbidden
|
||||
.new |>.status .forbidden
|
||||
|
||||
/--
|
||||
Creates a new HTTP Response builder with the 409 status code.
|
||||
-/
|
||||
def conflict : Builder :=
|
||||
.empty |>.status .conflict
|
||||
.new |>.status .conflict
|
||||
|
||||
/--
|
||||
Creates a new HTTP Response builder with the 503 status code.
|
||||
-/
|
||||
def serviceUnavailable : Builder :=
|
||||
.empty |>.status .serviceUnavailable
|
||||
.new |>.status .serviceUnavailable
|
||||
|
||||
end Response
|
||||
|
||||
@@ -94,4 +94,3 @@ def parseOrRoot (s : String) : Std.Http.URI.Path :=
|
||||
parse? s |>.getD { segments := #[], absolute := true }
|
||||
|
||||
end Std.Http.URI.Path
|
||||
|
||||
|
||||
@@ -174,19 +174,19 @@ opaque osEnviron : IO (Array (String × String))
|
||||
Gets the value of an environment variable.
|
||||
-/
|
||||
@[extern "lean_uv_os_getenv"]
|
||||
opaque osGetenv : String → IO (Option String)
|
||||
opaque osGetenv : @& String → IO (Option String)
|
||||
|
||||
/--
|
||||
Sets the value of an environment variable.
|
||||
-/
|
||||
@[extern "lean_uv_os_setenv"]
|
||||
opaque osSetenv : String → String → IO Unit
|
||||
opaque osSetenv : @& String → @& String → IO Unit
|
||||
|
||||
/--
|
||||
Unsets an environment variable.
|
||||
-/
|
||||
@[extern "lean_uv_os_unsetenv"]
|
||||
opaque osUnsetenv : String → IO Unit
|
||||
opaque osUnsetenv : @& String → IO Unit
|
||||
|
||||
/--
|
||||
Gets the hostname of the machine.
|
||||
|
||||
@@ -239,7 +239,7 @@ def ofFin' {lo : Nat} (fin : Fin (Nat.succ hi)) (h : lo ≤ hi) : Bounded.LE lo
|
||||
else ofNat' lo (And.intro (Nat.le_refl lo) h)
|
||||
|
||||
/--
|
||||
Creates a new `Bounded.LE` using a the modulus of a number.
|
||||
Creates a new `Bounded.LE` using the modulus of a number.
|
||||
-/
|
||||
@[inline]
|
||||
def byEmod (b : Int) (i : Int) (hi : i > 0) : Bounded.LE 0 (i - 1) := by
|
||||
@@ -252,7 +252,7 @@ def byEmod (b : Int) (i : Int) (hi : i > 0) : Bounded.LE 0 (i - 1) := by
|
||||
exact Int.emod_lt_of_pos b hi
|
||||
|
||||
/--
|
||||
Creates a new `Bounded.LE` using a the Truncating modulus of a number.
|
||||
Creates a new `Bounded.LE` using the Truncating modulus of a number.
|
||||
-/
|
||||
@[inline]
|
||||
def byMod (b : Int) (i : Int) (hi : 0 < i) : Bounded.LE (- (i - 1)) (i - 1) := by
|
||||
|
||||
@@ -976,6 +976,13 @@ static inline void lean_sarray_set_size(u_lean_obj_arg o, size_t sz) {
|
||||
}
|
||||
static inline uint8_t* lean_sarray_cptr(lean_object * o) { return lean_to_sarray(o)->m_data; }
|
||||
|
||||
LEAN_EXPORT bool lean_sarray_eq_cold(b_lean_obj_arg a1, b_lean_obj_arg a2);
|
||||
static inline bool lean_sarray_eq(b_lean_obj_arg a1, b_lean_obj_arg a2) {
|
||||
assert(lean_sarray_elem_size(a1) == lean_sarray_elem_size(a2));
|
||||
return a1 == a2 || (lean_sarray_size(a1) == lean_sarray_size(a2) && lean_sarray_eq_cold(a1, a2));
|
||||
}
|
||||
static inline uint8_t lean_sarray_dec_eq(b_lean_obj_arg a1, b_lean_obj_arg a2) { return lean_sarray_eq(a1, a2); }
|
||||
|
||||
/* Remark: expand sarray API after we add better support in the compiler */
|
||||
|
||||
/* ByteArray (special case of Array of Scalars) */
|
||||
|
||||
@@ -386,7 +386,7 @@ OPTIONS:
|
||||
--force-download redownload existing files
|
||||
|
||||
Downloads build outputs for packages in the workspace from a remote cache
|
||||
service. The cache service used can be specifed via the `--service` option.
|
||||
service. The cache service used can be specified via the `--service` option.
|
||||
Otherwise, Lake will the system default, or, if none is configured, Reservoir.
|
||||
See `lake cache services` for more information on how to configure services.
|
||||
|
||||
@@ -429,7 +429,7 @@ USAGE:
|
||||
|
||||
Uploads the input-to-output mappings contained in the specified file along
|
||||
with the corresponding output artifacts to a remote cache. The cache service
|
||||
used via be specified via `--service` option. If not specifed, Lake will used
|
||||
used can be specified via the `--service` option. If not specified, Lake will use
|
||||
the system default, or error if none is configured. See the help page of
|
||||
`lake cache services` for more information on how to configure services.
|
||||
|
||||
|
||||
@@ -446,7 +446,7 @@ protected def get : CliM PUnit := do
|
||||
logWarning endpointDeprecation
|
||||
if opts.mappingsOnly then
|
||||
error "`--mappings-only` requires services to be configured
|
||||
via the Lake system configuration (not enviroment variables)"
|
||||
via the Lake system configuration (not environment variables)"
|
||||
return .downloadService artifactEndpoint revisionEndpoint ws.lakeEnv.cacheService?
|
||||
| none, none =>
|
||||
return ws.defaultCacheService
|
||||
|
||||
@@ -765,12 +765,13 @@ where
|
||||
\n remote URL: {info.url}"
|
||||
match cfg.kind with
|
||||
| .get =>
|
||||
if let .ok size := out.getAs Nat "size_download" then
|
||||
if size > 0 then
|
||||
if let .ok contentType := out.getAs String "content_type" then
|
||||
if contentType != artifactContentType then
|
||||
if let .ok resp ← IO.FS.readFile info.path |>.toBaseIO then
|
||||
msg := s!"{msg}\nunexpected response:\n{resp}"
|
||||
unless code? matches .ok 404 do -- ignore response bodies on 404s
|
||||
if let .ok size := out.getAs Nat "size_download" then
|
||||
if size > 0 then
|
||||
if let .ok contentType := out.getAs String "content_type" then
|
||||
if contentType != artifactContentType then
|
||||
if let .ok resp ← IO.FS.readFile info.path |>.toBaseIO then
|
||||
msg := s!"{msg}\nunexpected response:\n{resp}"
|
||||
removeFileIfExists info.path
|
||||
| .put =>
|
||||
if let .ok size := out.getAs Nat "size_download" then
|
||||
@@ -787,7 +788,7 @@ private def transferArtifacts
|
||||
match cfg.kind with
|
||||
| .get =>
|
||||
cfg.infos.forM fun info => do
|
||||
h.putStrLn s!"url = {info.url}"
|
||||
h.putStrLn s!"url = {info.url.quote}"
|
||||
h.putStrLn s!"-o {info.path.toString.quote}"
|
||||
h.flush
|
||||
return #[
|
||||
@@ -798,7 +799,7 @@ private def transferArtifacts
|
||||
| .put =>
|
||||
cfg.infos.forM fun info => do
|
||||
h.putStrLn s!"-T {info.path.toString.quote}"
|
||||
h.putStrLn s!"url = {info.url}"
|
||||
h.putStrLn s!"url = {info.url.quote}"
|
||||
h.flush
|
||||
return #[
|
||||
"-Z", "-X", "PUT", "-L",
|
||||
@@ -827,6 +828,13 @@ private def transferArtifacts
|
||||
if s.didError then
|
||||
failure
|
||||
|
||||
private def reservoirArtifactsUrl (service : CacheService) (scope : CacheServiceScope) : String :=
|
||||
let endpoint :=
|
||||
match scope.impl with
|
||||
| .repo scope => appendScope s!"{service.impl.apiEndpoint}/repositories" scope
|
||||
| .str scope => appendScope s!"{service.impl.apiEndpoint}/packages" scope
|
||||
s!"{endpoint}/artifacts"
|
||||
|
||||
public def downloadArtifacts
|
||||
(descrs : Array ArtifactDescr) (cache : Cache)
|
||||
(service : CacheService) (scope : CacheServiceScope) (force := false)
|
||||
@@ -844,8 +852,68 @@ public def downloadArtifacts
|
||||
return s.push {url, path, descr}
|
||||
if infos.isEmpty then
|
||||
return
|
||||
let infos ← id do
|
||||
if service.isReservoir then
|
||||
-- Artifact cloud storage URLs are fetched in a single request
|
||||
-- to avoid hammering the Reservoir web host
|
||||
fetchUrls (service.reservoirArtifactsUrl scope) infos
|
||||
else return infos
|
||||
IO.FS.createDirAll cache.artifactDir
|
||||
transferArtifacts {scope, infos, kind := .get}
|
||||
where
|
||||
fetchUrls url infos := IO.FS.withTempFile fun h path => do
|
||||
let body := Json.arr <| infos.map (toJson ·.descr.hash)
|
||||
h.putStr body.compress
|
||||
h.flush
|
||||
let args := #[
|
||||
"-X", "POST", "-L", "-d", s!"@{path}",
|
||||
"--retry", "3", -- intermittent network errors can occur
|
||||
"-s", "-w", "%{stderr}%{json}\n",
|
||||
"-H", "Content-Type: application/json",
|
||||
]
|
||||
let args := Reservoir.lakeHeaders.foldl (· ++ #["-H", ·]) args
|
||||
let spawnArgs := {
|
||||
cmd := "curl", args := args.push url
|
||||
stdout := .piped, stderr := .piped
|
||||
}
|
||||
logVerbose (mkCmdLog spawnArgs)
|
||||
let {stdout, stderr, exitCode} ← IO.Process.output spawnArgs
|
||||
match Json.parse stdout >>= fromJson? with
|
||||
| .ok (resp : ReservoirResp (Array String)) =>
|
||||
match resp with
|
||||
| .data urls =>
|
||||
if h : infos.size = urls.size then
|
||||
let s := infos.size.fold (init := infos.toVector) fun i hi s =>
|
||||
s.set i {s[i] with url := urls[i]'(h ▸ hi)}
|
||||
return s.toArray
|
||||
else
|
||||
error s!"failed to fetch artifact URLs\
|
||||
\n POST {url}\
|
||||
\nIncorrect number of results: expected {infos.size}, got {urls.size}"
|
||||
| .error status message =>
|
||||
error s!"failed to fetch artifact URLs (status code: {status})\
|
||||
\n POST {url}\
|
||||
\nReservoir error: {message}"
|
||||
| .error _ =>
|
||||
match Json.parse stderr >>= fromJson? with
|
||||
| .ok (out : JsonObject) =>
|
||||
let mut msg := "failed to fetch artifact URLs"
|
||||
if let .ok code := out.getAs Nat "http_code" then
|
||||
msg := s!"{msg} (status code: {code})"
|
||||
msg := s!"{msg}\n POST {url}"
|
||||
if let .ok errMsg := out.getAs String "errormsg" then
|
||||
msg := s!"{msg}\n Transfer error: {errMsg}"
|
||||
unless stdout.isEmpty do
|
||||
msg := s!"{msg}\nstdout:\n{stdout.trimAsciiEnd}"
|
||||
logError msg
|
||||
logVerbose s!"curl JSON:\n{stderr.trimAsciiEnd}"
|
||||
| .error e =>
|
||||
logError s!"failed to fetch artifact URLs\
|
||||
\n POST {url}
|
||||
\nInvalid curl JSON: {e}; received: {stderr.trimAscii}"
|
||||
unless stdout.isEmpty do
|
||||
logWarning s!"curl produced unexpected output:\n{stdout.trimAsciiEnd}"
|
||||
error s!"curl exited with code {exitCode}"
|
||||
|
||||
@[deprecated "Deprecated without replacement." (since := "2026-02-27")]
|
||||
public def downloadOutputArtifacts
|
||||
|
||||
@@ -103,24 +103,6 @@ public instance : FromJson RegistryPkg := ⟨RegistryPkg.fromJson?⟩
|
||||
|
||||
end RegistryPkg
|
||||
|
||||
/-- A Reservoir API response object. -/
|
||||
public inductive ReservoirResp (α : Type u)
|
||||
| data (a : α)
|
||||
| error (status : Nat) (message : String)
|
||||
|
||||
public protected def ReservoirResp.fromJson? [FromJson α] (val : Json) : Except String (ReservoirResp α) := do
|
||||
let obj ← JsonObject.fromJson? val
|
||||
if let some (err : JsonObject) ← obj.get? "error" then
|
||||
let status ← err.get "status"
|
||||
let message ← err.get "message"
|
||||
return .error status message
|
||||
else if let some (val : Json) ← obj.get? "data" then
|
||||
.data <$> fromJson? val
|
||||
else
|
||||
.data <$> fromJson? val
|
||||
|
||||
public instance [FromJson α] : FromJson (ReservoirResp α) := ⟨ReservoirResp.fromJson?⟩
|
||||
|
||||
public def Reservoir.pkgApiUrl (lakeEnv : Lake.Env) (owner pkg : String) :=
|
||||
s!"{lakeEnv.reservoirApiUrl}/packages/{uriEncode owner}/{uriEncode pkg}"
|
||||
|
||||
|
||||
@@ -6,8 +6,9 @@ Authors: Mac Malone
|
||||
module
|
||||
|
||||
prelude
|
||||
public import Init.Prelude
|
||||
import Init.Data.Array.Basic
|
||||
public import Lake.Util.JsonObject
|
||||
|
||||
open Lean
|
||||
|
||||
namespace Lake
|
||||
|
||||
@@ -15,3 +16,23 @@ public def Reservoir.lakeHeaders : Array String := #[
|
||||
"X-Reservoir-Api-Version:1.0.0",
|
||||
"X-Lake-Registry-Api-Version:0.1.0"
|
||||
]
|
||||
|
||||
/-- A Reservoir API response object. -/
|
||||
public inductive ReservoirResp (α : Type u)
|
||||
| data (a : α)
|
||||
| error (status : Nat) (message : String)
|
||||
|
||||
public protected def ReservoirResp.fromJson? [FromJson α] (val : Json) : Except String (ReservoirResp α) := do
|
||||
if let .ok obj := JsonObject.fromJson? val then
|
||||
if let some (err : JsonObject) ← obj.get? "error" then
|
||||
let status ← err.get "status"
|
||||
let message ← err.get "message"
|
||||
return .error status message
|
||||
else if let some (val : Json) ← obj.get? "data" then
|
||||
.data <$> fromJson? val
|
||||
else
|
||||
.data <$> fromJson? val
|
||||
else
|
||||
.data <$> fromJson? val
|
||||
|
||||
public instance [FromJson α] : FromJson (ReservoirResp α) := ⟨ReservoirResp.fromJson?⟩
|
||||
|
||||
@@ -77,7 +77,7 @@ globs = ["Lake.*"]
|
||||
defaultFacets = ["static", "static.export"]
|
||||
# Load the previous stage's lake native code into lake's build process in order to prevent ABI
|
||||
# breakages from affecting bootstrapping.
|
||||
moreLeanArgs = ["--plugin", "${PREV_STAGE}/lib/lean/libLake_shared${CMAKE_SHARED_LIBRARY_SUFFIX}"]
|
||||
moreLeanArgs = ["--plugin", "${PREV_STAGE}/${CMAKE_RELATIVE_LIBRARY_OUTPUT_DIRECTORY}/libLake_shared${CMAKE_SHARED_LIBRARY_SUFFIX}"]
|
||||
|
||||
[[lean_lib]]
|
||||
name = "LakeMain"
|
||||
|
||||
@@ -5,6 +5,9 @@ Released under Apache 2.0 license as described in the file LICENSE.
|
||||
Author: Leonardo de Moura
|
||||
*/
|
||||
#include <limits>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <execinfo.h>
|
||||
#include "runtime/thread.h"
|
||||
#include "runtime/interrupt.h"
|
||||
#include "runtime/exception.h"
|
||||
@@ -16,6 +19,63 @@ namespace lean {
|
||||
LEAN_THREAD_VALUE(size_t, g_max_heartbeat, 0);
|
||||
LEAN_THREAD_VALUE(size_t, g_heartbeat, 0);
|
||||
|
||||
// --- check_system interval monitoring ---
|
||||
// When LEAN_CHECK_SYSTEM_INTERVAL_MS is set, warn on stderr if check_system
|
||||
// is not called within the given interval (in milliseconds of CPU time) on the
|
||||
// current thread. Uses CLOCK_THREAD_CPUTIME_ID so that IO waits (sleep, network,
|
||||
// disk) do not count towards the interval.
|
||||
|
||||
// 0 = disabled
|
||||
static unsigned g_check_system_interval_ms = 0;
|
||||
static bool g_check_system_interval_initialized = false;
|
||||
|
||||
static unsigned get_check_system_interval_ms() {
|
||||
if (!g_check_system_interval_initialized) {
|
||||
g_check_system_interval_initialized = true;
|
||||
if (const char * env = std::getenv("LEAN_CHECK_SYSTEM_INTERVAL_MS")) {
|
||||
g_check_system_interval_ms = std::atoi(env);
|
||||
}
|
||||
}
|
||||
return g_check_system_interval_ms;
|
||||
}
|
||||
|
||||
static uint64_t thread_cpu_time_ns() {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
|
||||
return static_cast<uint64_t>(ts.tv_sec) * 1000000000ULL + static_cast<uint64_t>(ts.tv_nsec);
|
||||
}
|
||||
|
||||
// Thread-local: last CPU time when check_system was called on this thread.
|
||||
LEAN_THREAD_VALUE(uint64_t, g_last_check_system_ns, 0);
|
||||
|
||||
static void check_system_interval(char const * component_name) {
|
||||
unsigned interval_ms = get_check_system_interval_ms();
|
||||
if (interval_ms > 0) {
|
||||
uint64_t now_ns = thread_cpu_time_ns();
|
||||
uint64_t last_ns = g_last_check_system_ns;
|
||||
g_last_check_system_ns = now_ns;
|
||||
if (last_ns != 0) {
|
||||
uint64_t elapsed_ms = (now_ns - last_ns) / 1000000;
|
||||
if (elapsed_ms > interval_ms) {
|
||||
fprintf(stderr,
|
||||
"[check_system] WARNING: %llu ms CPU time since last check_system call "
|
||||
"(component: %s)\n",
|
||||
(unsigned long long)elapsed_ms, component_name);
|
||||
// Reset timer after printing to avoid backtrace overhead cascading
|
||||
g_last_check_system_ns = thread_cpu_time_ns();
|
||||
void * bt_buf[64];
|
||||
int nptrs = backtrace(bt_buf, 64);
|
||||
backtrace_symbols_fd(bt_buf, nptrs, 2); // fd 2 = stderr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" LEAN_EXPORT obj_res lean_check_system_interval(b_lean_obj_arg component_name) {
|
||||
check_system_interval(lean_string_cstr(component_name));
|
||||
return lean_io_result_mk_ok(lean_box(0));
|
||||
}
|
||||
|
||||
extern "C" LEAN_EXPORT obj_res lean_internal_get_default_max_heartbeat() {
|
||||
#ifdef LEAN_DEFAULT_MAX_HEARTBEAT
|
||||
return lean_box(LEAN_DEFAULT_MAX_HEARTBEAT);
|
||||
@@ -51,6 +111,9 @@ void check_heartbeat() {
|
||||
inc_heartbeat();
|
||||
if (g_max_heartbeat > 0 && g_heartbeat > g_max_heartbeat)
|
||||
throw_heartbeat_exception();
|
||||
if (g_heartbeat % 1024 == 0) {
|
||||
check_system("heartbeat");
|
||||
}
|
||||
}
|
||||
|
||||
LEAN_THREAD_VALUE(lean_object *, g_cancel_tk, nullptr);
|
||||
@@ -71,6 +134,7 @@ void check_interrupted() {
|
||||
}
|
||||
|
||||
void check_system(char const * component_name, bool do_check_interrupted) {
|
||||
check_system_interval(component_name);
|
||||
check_stack(component_name);
|
||||
check_memory(component_name);
|
||||
if (do_check_interrupted) {
|
||||
|
||||
@@ -2078,6 +2078,11 @@ extern "C" LEAN_EXPORT bool lean_string_eq_cold(b_lean_obj_arg s1, b_lean_obj_ar
|
||||
return std::memcmp(lean_string_cstr(s1), lean_string_cstr(s2), lean_string_size(s1)) == 0;
|
||||
}
|
||||
|
||||
extern "C" LEAN_EXPORT bool lean_sarray_eq_cold(b_lean_obj_arg a1, b_lean_obj_arg a2) {
|
||||
size_t len = lean_sarray_elem_size(a1) * lean_sarray_size(a1);
|
||||
return std::memcmp(lean_sarray_cptr(a1), lean_sarray_cptr(a2), len) == 0;
|
||||
}
|
||||
|
||||
bool string_eq(object * s1, char const * s2) {
|
||||
if (lean_string_size(s1) != strlen(s2) + 1)
|
||||
return false;
|
||||
|
||||
@@ -31,7 +31,7 @@ extern "C" LEAN_EXPORT lean_obj_res lean_uv_get_process_title() {
|
||||
return lean_io_result_mk_ok(lean_title);
|
||||
}
|
||||
|
||||
// Std.Internal.UV.System.setProcessTitle : String → IO Unit
|
||||
// Std.Internal.UV.System.setProcessTitle : @& String → IO Unit
|
||||
extern "C" LEAN_EXPORT lean_obj_res lean_uv_set_process_title(b_obj_arg title) {
|
||||
const char* title_str = lean_string_cstr(title);
|
||||
if (strlen(title_str) != lean_string_size(title) - 1) {
|
||||
@@ -124,7 +124,7 @@ extern "C" LEAN_EXPORT lean_obj_res lean_uv_cwd() {
|
||||
return lean_io_result_mk_ok(lean_cwd);
|
||||
}
|
||||
|
||||
// Std.Internal.UV.System.chdir : String → IO Unit
|
||||
// Std.Internal.UV.System.chdir : @& String → IO Unit
|
||||
extern "C" LEAN_EXPORT lean_obj_res lean_uv_chdir(b_obj_arg path) {
|
||||
const char* path_str = lean_string_cstr(path);
|
||||
if (strlen(path_str) != lean_string_size(path) - 1) {
|
||||
@@ -271,7 +271,7 @@ extern "C" LEAN_EXPORT lean_obj_res lean_uv_os_environ() {
|
||||
return lean_io_result_mk_ok(env_array);
|
||||
}
|
||||
|
||||
// Std.Internal.UV.System.osGetenv : String → IO (Option String)
|
||||
// Std.Internal.UV.System.osGetenv : @& String → IO (Option String)
|
||||
extern "C" LEAN_EXPORT lean_obj_res lean_uv_os_getenv(b_obj_arg name) {
|
||||
const char* name_str = lean_string_cstr(name);
|
||||
if (strlen(name_str) != lean_string_size(name) - 1) {
|
||||
@@ -313,7 +313,7 @@ extern "C" LEAN_EXPORT lean_obj_res lean_uv_os_getenv(b_obj_arg name) {
|
||||
}
|
||||
|
||||
|
||||
// Std.Internal.UV.System.osSetenv : String → String → IO Unit
|
||||
// Std.Internal.UV.System.osSetenv : @& String → @& String → IO Unit
|
||||
extern "C" LEAN_EXPORT lean_obj_res lean_uv_os_setenv(b_obj_arg name, b_obj_arg value) {
|
||||
const char* name_str = lean_string_cstr(name);
|
||||
const char* value_str = lean_string_cstr(value);
|
||||
@@ -333,7 +333,7 @@ extern "C" LEAN_EXPORT lean_obj_res lean_uv_os_setenv(b_obj_arg name, b_obj_arg
|
||||
return lean_io_result_mk_ok(lean_box(0));
|
||||
}
|
||||
|
||||
// Std.Internal.UV.System.osUnsetenv : String → IO Unit
|
||||
// Std.Internal.UV.System.osUnsetenv : @& String → IO Unit
|
||||
extern "C" LEAN_EXPORT lean_obj_res lean_uv_os_unsetenv(b_obj_arg name) {
|
||||
const char* name_str = lean_string_cstr(name);
|
||||
if (strlen(name_str) != lean_string_size(name) - 1) {
|
||||
@@ -641,21 +641,21 @@ extern "C" LEAN_EXPORT lean_obj_res lean_uv_os_environ() {
|
||||
);
|
||||
}
|
||||
|
||||
// Std.Internal.UV.System.osGetenv : String → IO (Option String)
|
||||
// Std.Internal.UV.System.osGetenv : @& String → IO (Option String)
|
||||
extern "C" LEAN_EXPORT lean_obj_res lean_uv_os_getenv(b_obj_arg name) {
|
||||
lean_always_assert(
|
||||
false && ("Please build a version of Lean4 with libuv to invoke this.")
|
||||
);
|
||||
}
|
||||
|
||||
// Std.Internal.UV.System.osSetenv : String → String → IO Unit
|
||||
// Std.Internal.UV.System.osSetenv : @& String → @& String → IO Unit
|
||||
extern "C" LEAN_EXPORT lean_obj_res lean_uv_os_setenv(b_obj_arg name, b_obj_arg value) {
|
||||
lean_always_assert(
|
||||
false && ("Please build a version of Lean4 with libuv to invoke this.")
|
||||
);
|
||||
}
|
||||
|
||||
// Std.Internal.UV.System.osUnsetenv : String → IO Unit
|
||||
// Std.Internal.UV.System.osUnsetenv : @& String → IO Unit
|
||||
extern "C" LEAN_EXPORT lean_obj_res lean_uv_os_unsetenv(b_obj_arg name) {
|
||||
lean_always_assert(
|
||||
false && ("Please build a version of Lean4 with libuv to invoke this.")
|
||||
|
||||
@@ -162,7 +162,7 @@ else
|
||||
-Wl,--whole-archive ${LIB}/temp/Lean.*o.export ${LIB}/temp/libleanshell.a -Wl,--no-whole-archive -Wl,--start-group -lInit -lStd -lLean -lleancpp -Wl,--end-group ${CMAKE_BINARY_DIR}/runtime/libleanrt_initial-exec.a ${LEANSHARED_LINKER_FLAGS} ${TOOLCHAIN_SHARED_LINKER_FLAGS} ${LEANC_OPTS}
|
||||
endif
|
||||
endif
|
||||
ifeq "${CMAKE_BUILD_TYPE}" "Release"
|
||||
ifeq "${STRIP_BINARIES}" "ON"
|
||||
ifeq "${CMAKE_SYSTEM_NAME}" "Linux"
|
||||
# We only strip like this on Linux for now as our other platforms already seem to exclude the
|
||||
# unexported symbols by default
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user