Compare commits

...

1 Commits

Author SHA1 Message Date
Leonardo de Moura
02b1badbde feat: universe constraint approximations
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.
2024-04-23 17:44:32 -07:00
5 changed files with 89 additions and 9 deletions

View File

@@ -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.
@@ -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
@@ -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

View File

@@ -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
@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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`