Skip to content

Commit

Permalink
feat: universe constraint approximations (#3981)
Browse files Browse the repository at this point in the history
We add a new configuration flag for `isDefEq`:
`Meta.Config.univApprox`.
When it is true, we approximate the solution for universe constraints
such as
- `u =?= max u ?v`, we use `?v := u`, and ignore the solution `?v := 0`.
- `max u v =?= max u ?w`, we use `?w := v`, and ignore the solution `?w
:= max u v`.

We only apply these approximations when there the contraints cannot be
postponed anymore. These approximations prevent error messages such as
```
error: stuck at solving universe constraint
  max u ?u.3430 =?= u
```
This kind of error seems to appear in several Mathlib files.

We currently do not use these approximations while synthesizing type
class instances.
  • Loading branch information
leodemoura authored Apr 24, 2024
1 parent 605cecd commit 1630d9b
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 9 deletions.
17 changes: 17 additions & 0 deletions src/Lean/Meta/Basic.lean
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ structure Config where
trackZetaDelta : Bool := false
/-- Eta for structures configuration mode. -/
etaStruct : EtaStructMode := .all
/--
When `univApprox` is set to true,
we use approximations when solving postponed universe constraints.
Examples:
- `max u ?v =?= u` is solved with `?v := u` and ignoring the solution `?v := 0`.
- `max u w =?= mav u ?v` is solved with `?v := w` ignoring the solution `?v := max u w`
-/
univApprox : Bool := true

/--
Function parameter information cache.
Expand Down Expand Up @@ -300,6 +308,11 @@ structure Context where
A predicate to control whether a constant can be unfolded or not at `whnf`.
Note that we do not cache results at `whnf` when `canUnfold?` is not `none`. -/
canUnfold? : Option (Config → ConstantInfo → CoreM Bool) := none
/--
When `Config.univApprox := true`, this flag is set to `true` when there is no
progress processing universe constraints.
-/
univApprox : Bool := false

/--
The `MetaM` monad is a core component of Lean's metaprogramming framework, facilitating the
Expand Down Expand Up @@ -1690,6 +1703,10 @@ partial def processPostponed (mayPostpone : Bool := true) (exceptionOnFailure :=
return true
else if numPostponed' < numPostponed then
loop
-- If we cannot pospone anymore, `Config.univApprox := true`, but we haven't tried universe approximations yet,
-- then try approximations before failing.
else if !mayPostpone && (← getConfig).univApprox && !(← read).univApprox then
withReader (fun ctx => { ctx with univApprox := true }) loop
else
trace[Meta.isLevelDefEq.postponed] "no progress solving pending is-def-eq level constraints"
return mayPostpone
Expand Down
54 changes: 48 additions & 6 deletions src/Lean/Meta/LevelDefEq.lean
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,62 @@ namespace Lean.Meta
That is, `lvl` is a proper level subterm of some `u_i`. -/
private def strictOccursMax (lvl : Level) : Level → Bool
| Level.max u v => visit u || visit v
| _ => false
| _ => false
where
visit : Level → Bool
| Level.max u v => visit u || visit v
| u => u != lvl && lvl.occurs u
| u => u != lvl && lvl.occurs u

/-- `mkMaxArgsDiff mvarId (max u_1 ... (mvar mvarId) ... u_n) v` => `max v u_1 ... u_n` -/
private def mkMaxArgsDiff (mvarId : LMVarId) : Level → Level → Level
| Level.max u v, acc => mkMaxArgsDiff mvarId v <| mkMaxArgsDiff mvarId u acc
| l@(Level.mvar id), acc => if id != mvarId then mkLevelMax' acc l else acc
| l, acc => mkLevelMax' acc l
| l, acc => mkLevelMax' acc l

/--
Solve `?m =?= max ?m v` by creating a fresh metavariable `?n`
and assigning `?m := max ?n v` -/
Solves `?m =?= max ?m v` by creating a fresh metavariable `?n`
and assigning `?m := max ?n v`
-/
private def solveSelfMax (mvarId : LMVarId) (v : Level) : MetaM Unit := do
assert! v.isMax
let n ← mkFreshLevelMVar
assignLevelMVar mvarId <| mkMaxArgsDiff mvarId v n

/--
Returns true if `v` is `max u ?m` (or variant). That is, we solve `u =?= max u ?m` as `?m := u`.
This is an approximation. For example, we ignore the solution `?m := 0`.
-/
-- TODO: investigate whether we need to improve this approximation.
private def tryApproxSelfMax (u v : Level) : MetaM Bool := do
match v with
| .max v' (.mvar mvarId) => solve v' mvarId
| .max (.mvar mvarId) v' => solve v' mvarId
| _ => return false
where
solve (v' : Level) (mvarId : LMVarId) : MetaM Bool := do
if u == v' then
assignLevelMVar mvarId u
return true
else
return false

/--
Returns true if `u` of the form `max u₁ u₂` and `v` of the form `max u₁ ?w` (or variant).
That is, we solve `max u₁ u₂ =?= max u₁ ?v` as `?v := u₂`.
This is an approximation. For example, we ignore the solution `?w := max u₁ u₂`.
-/
-- TODO: investigate whether we need to improve this approximation.
private def tryApproxMaxMax (u v : Level) : MetaM Bool := do
match u, v with
| .max u₁ u₂, .max v' (.mvar mvarId) => solve u₁ u₂ v' mvarId
| .max u₁ u₂, .max (.mvar mvarId) v' => solve u₁ u₂ v' mvarId
| _, _ => return false
where
solve (u₁ u₂ v' : Level) (mvarId : LMVarId) : MetaM Bool := do
if u₁ == v' then assignLevelMVar mvarId u₂; return true
else if u₂ == v' then assignLevelMVar mvarId u₁; return true
else return false

private def postponeIsLevelDefEq (lhs : Level) (rhs : Level) : MetaM Unit := do
let ref ← getRef
let ctx ← read
Expand Down Expand Up @@ -82,7 +118,13 @@ mutual
match (← Meta.decLevel? v) with
| some v => Bool.toLBool <$> isLevelDefEqAux u v
| none => return LBool.undef
| _, _ => return LBool.undef
| _, _ =>
if (← read).univApprox then
if (← tryApproxSelfMax u v) then
return LBool.true
if (← tryApproxMaxMax u v) then
return LBool.true
return LBool.undef

@[export lean_is_level_def_eq]
partial def isLevelDefEqAuxImpl : Level → Level → MetaM Bool
Expand Down
2 changes: 1 addition & 1 deletion src/Lean/Meta/SynthInstance.lean
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ def synthInstance? (type : Expr) (maxResultSize? : Option Nat := none) : MetaM (
withTraceNode `Meta.synthInstance
(return m!"{exceptOptionEmoji ·} {← instantiateMVars type}") do
withConfig (fun config => { config with isDefEqStuckEx := true, transparency := TransparencyMode.instances,
foApprox := true, ctxApprox := true, constApprox := false }) do
foApprox := true, ctxApprox := true, constApprox := false, univApprox := false }) do
let localInsts ← getLocalInstances
let type ← instantiateMVars type
let type ← preprocess type
Expand Down
5 changes: 3 additions & 2 deletions tests/lean/run/3965.lean
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,10 @@ theorem principal_nfp_blsub₂ (op : Ordinal → Ordinal → Ordinal) (o : Ordin
· refine ⟨n+1, ?_⟩
rw [Function.iterate_succ']
-- after https://github.com/leanprover/lean4/pull/3965 this requires `lt_blsub₂.{u}` or we get
-- `stuck at solving universe constraint max u ?u =?= u`
-- `stuck at solving universe constraint max u ?v =?= u`
-- Note that there are two solutions: 0 and u. Both of them work.
exact lt_blsub₂.{u} (@fun a _ b _ => op a b) (lt_of_lt_of_le hm h) hn
-- However, when `Meta.Config.univApprox := true`, we solve using `?v := u`
exact lt_blsub₂ (@fun a _ b _ => op a b) (lt_of_lt_of_le hm h) hn
· sorry

-- Trying again with 0
Expand Down
20 changes: 20 additions & 0 deletions tests/lean/run/univCnstrApprox.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
universe u v

-- This is a mock-up of the `HasLimitsOfSize` typeclass in mathlib4.
class HLOS.{a,b} (C : Type b) where
P : Type a

-- We only have an instance when there is a "universe inequality".
instance HLOS_max : HLOS.{a} (Type max a b) := sorry

-- In mathlib4 we currently make use of the following workaround:
abbrev TypeMax := Type max u v

instance (priority := high) HLOS_max' : HLOS.{a} (TypeMax.{a, b}) := sorry

example : HLOS.{a} (TypeMax.{a, b}) := HLOS_max'.{a} -- Success
example : HLOS.{a} (TypeMax.{a, b}) := inferInstance -- Success

-- We solve the following examples using approximations
example : Type max v u = TypeMax.{v} := rfl -- Previously failed with: `max u v =?= max v ?u`
example : Type max v u = TypeMax.{u} := rfl -- Previously failed with: `max u v =?= max u ?u`

0 comments on commit 1630d9b

Please sign in to comment.