Lean 4 fork for HoTT-compatible kernel extensions (Path types, transport, HITs). Maintained against upstream leanprover/lean4.
Find a file
Henrik Böving fe96911368
feat: proper recursive specialization (#11479)
This PR enables the specializer to also recursively specialize in some
non trivial higher order situations.

The main motivation for this change is the upcoming changes to do
notation by sgraf. In there he uses combinators such as
```lean
@[specialize, expose]
def List.newForIn {α β γ} (l : List α) (b : β) (kcons : α → (β → γ) → β → γ) (knil : β → γ) : γ :=
  match l with
  | []     => knil b
  | a :: l => kcons a (l.newForIn · kcons knil) b
```
in programs such as
```lean
def testing :=
  let x := 42;
  List.newForIn (β := Nat) (γ := Id Nat)
    [1,2,3]
    x
    (fun i kcontinue s =>
      let x := s;
      List.newForIn
        [i:10].toList x
        (fun j kcontinue s =>
          let x := s;
          let x := x + i + j;
          kcontinue x)
        kcontinue)
    pure
```
inspecting this IR right before we get to the specializer in the current
compiler we get:
```
[Compiler.eagerLambdaLifting] size: 22
    def testing : Nat :=
      fun _f.1 _y.2 : Nat :=
        return _y.2;
      let x := 42;
      let _x.3 := 1;
      fun _f.4 i kcontinue s : Nat :=
        fun _f.5 j kcontinue s : Nat :=
          let _x.6 := Nat.add s i;
          let x := Nat.add _x.6 j;
          let _x.7 := kcontinue x;
          return _x.7;
        let _x.8 := 10;
        let _x.9 := Nat.sub _x.8 i;
        let _x.10 := Nat.add _x.9 _x.3;
        let _x.11 := 1;
        let _x.12 := Nat.sub _x.10 _x.11;
        let _x.13 := Nat.mul _x.3 _x.12;
        let _x.14 := Nat.add i _x.13;
        let _x.15 := @List.nil _;
        let _x.16 := List.range'TR.go _x.3 _x.12 _x.14 _x.15;
        let _x.17 := @List.newForIn _ _ _ _x.16 s _f.5 kcontinue;
        return _x.17;
      let _x.18 := 2;
      let _x.19 := 3;
      let _x.20 := @List.nil _;
      let _x.21 := @List.cons _ _x.19 _x.20;
      let _x.22 := @List.cons _ _x.18 _x.21;
      let _x.23 := @List.cons _ _x.3 _x.22;
      let _x.24 := @List.newForIn _ _ _ _x.23 x _f.4 _f.1;
      return _x.24 
```
Here the `kcontinue` higher order functions pose a special challenge
because they delay the discovery of new specialization opportunities.
Inspecting the IR after the current specializer (and a cleanup simp
step) we get functions that look as follows:
```
 [simp] size: 7
      def List.newForIn._at_.testing.spec_0 i kcontinue l b : Nat :=
        cases l : Nat
        | List.nil =>
          let _x.1 := kcontinue b;
          return _x.1
        | List.cons head.2 tail.3 =>
          let _x.4 := Nat.add b i;
          let x := Nat.add _x.4 head.2;
          let _x.5 := List.newForIn._at_.testing.spec_0 i kcontinue tail.3 x;
          return _x.5 
  [simp] size: 14
      def List.newForIn._at_.List.newForIn._at_.testing.spec_1.spec_1 _x.1 l b : Nat :=
        cases l : Nat
        | List.nil =>
          return b
        | List.cons head.2 tail.3 =>
          fun _f.4 x.5 : Nat :=
            let _x.6 := List.newForIn._at_.List.newForIn._at_.testing.spec_1.spec_1 _x.1 tail.3 x.5;
            return _x.6;
          let _x.7 := 10;
          let _x.8 := Nat.sub _x.7 head.2;
          let _x.9 := Nat.add _x.8 _x.1;
          let _x.10 := 1;
          let _x.11 := Nat.sub _x.9 _x.10;
          let _x.12 := Nat.mul _x.1 _x.11;
          let _x.13 := Nat.add head.2 _x.12;
          let _x.14 := @List.nil _;
          let _x.15 := List.range'TR.go _x.1 _x.11 _x.13 _x.14;
          let _x.16 := List.newForIn._at_.testing.spec_0 head.2 _f.4 _x.15 b;
          return _x.16
```
Observe that the specializer decided to abstract over `kcontinue`
instead of specializing further recursively. Thus this tight loop is now
going through an indirect call.

This PR now changes the specializer somewhat fundamentally to handle
situations like this. The most notable change is going to a fixpoint
loop of:
1. Specialize all current declarations in the worklist
2. If a declaration
- succeeded in specializing run the simplifier on it and put it back
onto the worklist
    - if it didn't don't put it back onto the worklist anymore
3. Put all newly generated specialisations on the worklist
4. Recompute fixed parameters for the current SCC
5. Repeat until the worklist is empty

Furthermore, declarations that were already specialized:
- only consider `fixedHO` parameters for specialization, in order to
avoid termination issues with repeated specialization and abstraction of
type class parameters under binders
- recursively specialized declarations only allow specialization if at
least one of their fixedHO arguments is not a parameter itself. The
reason for allowing this in first generation specialization is that we
refrain from specializing inside the body of a declaration marked as
`@[specialize]`. Thus we need to specialize them even if their arguments
don't actually contain anything of interest in order to ensure that type
classes etc. are correctly cleaned up within their bodies.

There is one last trade-off to consider. When specializing code
generated by the new do elaborator we sometimes generate intermediate
specializations that are not actually part of any call graph after we
are done specializing. We could in principle detect these functions and
delete them but having them in cache is potentially helpful for further
specializations later. Once the new do elaborator lands we plan to test
this trade-off.

Closes #10924
2025-12-17 11:05:24 +00:00
.claude chore: add guidance to not merge PRs autonomously in release command (#11661) 2025-12-14 05:17:42 +00:00
.github fix: remove batteries tag check from PR mathlib CI (#11707) 2025-12-17 08:53:04 +00:00
doc chore: update release docs/scripts for lean-fro.org (#11648) 2025-12-13 11:06:17 +00:00
images chore(CMakeLists.txt): move Lean logo to make sure we can test leanemacs without installing Lean 2015-01-31 17:38:49 -08:00
releases_drafts chore: add release draft for the module system (#11359) 2025-11-26 15:01:07 +00:00
script chore: add missing repositories to release_repos.yml (#11668) 2025-12-17 02:33:53 +00:00
src feat: proper recursive specialization (#11479) 2025-12-17 11:05:24 +00:00
stage0 feat: proper recursive specialization (#11479) 2025-12-17 11:05:24 +00:00
tests feat: proper recursive specialization (#11479) 2025-12-17 11:05:24 +00:00
.gitattributes doc: grove: update and add String data (#11551) 2025-12-08 16:49:37 +00:00
.gitignore chore: add .vscode/settings.json to .gitignore (#10795) 2025-10-16 07:08:41 +00:00
.gitpod.Dockerfile chore: add gitpod configuration (#6382) 2024-12-15 21:38:13 +00:00
.gitpod.yml chore: add gitpod configuration (#6382) 2024-12-15 21:38:13 +00:00
.ignore chore: ignore stage0/ (for rg etc.) 2022-03-18 15:28:20 +01:00
CMakeLists.txt fix: cadical dynamic dependencies (#11475) 2025-12-02 13:54:26 +00:00
CMakePresets.json chore: CI: enable leak sanitizer again (#11339) 2025-11-27 18:32:35 +00:00
CODEOWNERS chore: make @hargoniX code owner of the compiler (#10732) 2025-10-10 04:43:38 +00:00
CONTRIBUTING.md doc: triage 2024-07-26 18:24:06 +02:00
flake.lock chore: robustify Nix shell (#8141) 2025-04-28 15:08:32 +00:00
flake.nix chore: use lld if available for building core (#10694) 2025-10-08 16:47:30 +00:00
lean-toolchain doc: VS Code dev setup (#2961) 2023-11-30 08:35:03 +00:00
lean.code-workspace chore: lean.code-workspace: fix terminal cwd (#10802) 2025-10-16 20:19:12 +00:00
LICENSE chore: remove LICENSE header that confused GitHub 2021-11-18 09:42:35 +01:00
LICENSES feat: ship cadical (#4325) 2024-08-23 09:13:27 +00:00
README.md doc: update URLs that are currently pointing to redirects (#10397) 2025-09-17 15:50:07 +00:00
RELEASES.md chore: updates to release_checklist.md (#7817) 2025-04-04 03:45:36 +00:00

This is the repository for Lean 4.

About

Installation

See Install Lean.

Contributing

Please read our Contribution Guidelines first.

Building from Source

See Building Lean.