Files
lean4/src/Lean/Level.lean
Kim Morrison 66bc9ae177 chore: deprecate levelZero and levelOne (#12720)
This PR deprecates `levelZero` in favor of `Level.zero` and `levelOne`
in favor of the new `Level.one`, and updates all usages throughout the
codebase. The `levelZero` alias was previously required for computed
field `data` to work, but this is no longer needed.

🤖 Prepared with Claude Code
2026-03-04 01:03:08 +00:00

666 lines
22 KiB
Lean4
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/-
Copyright (c) 2018 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 Init.Data.Array.QSort
public import Lean.Data.PersistentHashSet
public import Lean.Hygiene
public import Init.Data.Option.Coe
import Init.Data.Nat.Linear
public section
def Nat.imax (n m : Nat) : Nat :=
if m = 0 then 0 else Nat.max n m
namespace Lean
/--
Cached hash code, cached results, and other data for `Level`.
hash : 32-bits
hasMVar : 1-bit
hasParam : 1-bit
depth : 24-bits -/
@[expose] def Level.Data := UInt64
instance : Inhabited Level.Data :=
inferInstanceAs (Inhabited UInt64)
def Level.Data.hash (c : Level.Data) : UInt64 :=
c.toUInt32.toUInt64
instance : BEq Level.Data :=
fun (a b : UInt64) => a == b
def Level.Data.depth (c : Level.Data) : UInt32 :=
(c.shiftRight 40).toUInt32
def Level.Data.hasMVar (c : Level.Data) : Bool :=
((c.shiftRight 32).land 1) == 1
def Level.Data.hasParam (c : Level.Data) : Bool :=
((c.shiftRight 33).land 1) == 1
@[extern "lean_level_mk_data"]
opaque Level.mkData (h : UInt64) (depth : Nat := 0) (hasMVar hasParam : Bool := false) : Level.Data
instance : Repr Level.Data where
reprPrec v prec := Id.run do
let mut r := "Level.mkData " ++ toString v.hash
if v.depth != 0 then
r := r ++ " (depth := " ++ toString v.depth ++ ")"
if v.hasMVar then
r := r ++ " (hasMVar := " ++ toString v.hasMVar ++ ")"
if v.hasParam then
r := r ++ " (hasParam := " ++ toString v.hasParam ++ ")"
Repr.addAppParen r prec
open Level
/-- Universe level metavariable Id -/
structure LevelMVarId where
name : Name
deriving Inhabited, BEq, Hashable, Repr
/-- Short for `LevelMVarId` -/
abbrev LMVarId := LevelMVarId
instance : Repr LMVarId where
reprPrec n p := reprPrec n.name p
@[expose] def LMVarIdSet := Std.TreeSet LMVarId (Name.quickCmp ·.name ·.name)
deriving Inhabited, EmptyCollection
instance [Monad m] : ForIn m LMVarIdSet LMVarId := inferInstanceAs (ForIn _ (Std.TreeSet _ _) ..)
@[expose] def LMVarIdMap (α : Type) := Std.TreeMap LMVarId α (Name.quickCmp ·.name ·.name)
instance : EmptyCollection (LMVarIdMap α) := inferInstanceAs (EmptyCollection (Std.TreeMap _ _ _))
instance [Monad m] : ForIn m (LMVarIdMap α) (LMVarId × α) := inferInstanceAs (ForIn _ (Std.TreeMap _ _ _) ..)
instance : Inhabited (LMVarIdMap α) where
default := {}
inductive Level where
| zero : Level
| succ : Level Level
| max : Level Level Level
| imax : Level Level Level
| param : Name Level
| mvar : LMVarId Level
with
@[computed_field] data : Level Data
| .zero => mkData 2221 0 false false
| .mvar mvarId => mkData (mixHash 2237 <| hash mvarId) 0 true false
| .param name => mkData (mixHash 2239 <| hash name) 0 false true
| .succ u => mkData (mixHash 2243 <| u.data.hash) (u.data.depth.toNat + 1) u.data.hasMVar u.data.hasParam
| .max u v => mkData (mixHash 2251 <| mixHash (u.data.hash) (v.data.hash)) (Nat.max u.data.depth.toNat v.data.depth.toNat + 1)
(u.data.hasMVar || v.data.hasMVar) (u.data.hasParam || v.data.hasParam)
| .imax u v => mkData (mixHash 2267 <| mixHash (u.data.hash) (v.data.hash)) (Nat.max u.data.depth.toNat v.data.depth.toNat + 1)
(u.data.hasMVar || v.data.hasMVar) (u.data.hasParam || v.data.hasParam)
deriving Inhabited, Repr
namespace Level
protected def hash (u : Level) : UInt64 :=
u.data.hash
instance : Hashable Level := Level.hash
def depth (u : Level) : Nat :=
u.data.depth.toNat
def hasMVar (u : Level) : Bool :=
u.data.hasMVar
def hasParam (u : Level) : Bool :=
u.data.hasParam
@[export lean_level_hash] def hashEx (u : Level) : UInt32 := hash u |>.toUInt32
@[export lean_level_has_mvar] def hasMVarEx : Level Bool := hasMVar
@[export lean_level_has_param] def hasParamEx : Level Bool := hasParam
@[export lean_level_depth] def depthEx (u : Level) : UInt32 := u.data.depth
end Level
@[deprecated Level.zero (since := "2026-02-27")] -- This was previously required in order to get the computed field `data` to work, but it is no longer needed.
abbrev levelZero := Level.zero
def mkLevelMVar (mvarId : LMVarId) :=
Level.mvar mvarId
def mkLevelParam (name : Name) :=
Level.param name
@[expose] def mkLevelSucc (u : Level) :=
Level.succ u
def mkLevelMax (u v : Level) :=
Level.max u v
def mkLevelIMax (u v : Level) :=
Level.imax u v
abbrev Level.one := mkLevelSucc .zero
@[deprecated Level.one (since := "2026-02-27")]
abbrev levelOne := Level.one
@[export lean_level_mk_zero] def mkLevelZeroEx : Unit Level := fun _ => .zero
@[export lean_level_mk_succ] def mkLevelSuccEx : Level Level := mkLevelSucc
@[export lean_level_mk_mvar] def mkLevelMVarEx : LMVarId Level := mkLevelMVar
@[export lean_level_mk_param] def mkLevelParamEx : Name Level := mkLevelParam
@[export lean_level_mk_max] def mkLevelMaxEx : Level Level Level := mkLevelMax
@[export lean_level_mk_imax] def mkLevelIMaxEx : Level Level Level := mkLevelIMax
namespace Level
def isZero : Level Bool
| zero => true
| _ => false
def isSucc : Level Bool
| succ .. => true
| _ => false
def isMax : Level Bool
| max .. => true
| _ => false
def isIMax : Level Bool
| imax .. => true
| _ => false
def isMaxIMax : Level Bool
| max .. => true
| imax .. => true
| _ => false
def isParam : Level Bool
| param .. => true
| _ => false
def isMVar : Level Bool
| mvar .. => true
| _ => false
def mvarId! : Level LMVarId
| mvar mvarId => mvarId
| _ => panic! "metavariable expected"
/-- If result is true, then forall assignments `A` which assigns all parameters and metavariables occurring
in `l`, `l[A] != zero` -/
def isNeverZero : Level Bool
| zero => false
| param .. => false
| mvar .. => false
| succ .. => true
| max l₁ l₂ => isNeverZero l₁ || isNeverZero l₂
| imax _ l₂ => isNeverZero l₂
/--
Returns true if and only if `l` evaluates to zero for all instantiations of parameters and
meta-variables.
-/
def isAlwaysZero : Level Bool
| zero => true
| param .. => false
| mvar .. => false
| succ .. => false
| max l₁ l₂ => isAlwaysZero l₁ && isAlwaysZero l₂
| imax _ l₂ => isAlwaysZero l₂
@[expose, implicit_reducible] def ofNat : Nat Level
| 0 => Level.zero
| n+1 => mkLevelSucc (ofNat n)
instance instOfNat (n : Nat) : OfNat Level n where
ofNat := ofNat n
def addOffsetAux : Nat Level Level
| 0, u => u
| (n+1), u => addOffsetAux n (mkLevelSucc u)
def addOffset (u : Level) (n : Nat) : Level :=
u.addOffsetAux n
def isExplicit : Level Bool
| zero => true
| succ u => !u.hasMVar && !u.hasParam && isExplicit u
| _ => false
def getOffsetAux : Level Nat Nat
| succ u , r => getOffsetAux u (r+1)
| _, r => r
def getOffset (lvl : Level) : Nat :=
getOffsetAux lvl 0
def getLevelOffset : Level Level
| succ u => getLevelOffset u
| u => u
def toNat (lvl : Level) : Option Nat :=
match lvl.getLevelOffset with
| zero => lvl.getOffset
| _ => none
@[extern "lean_level_eq"]
protected opaque beq (a : @& Level) (b : @& Level) : Bool
instance : BEq Level := Level.beq
/-- `occurs u l` return `true` iff `u` occurs in `l`. -/
def occurs : Level Level Bool
| u, v@(succ v₁ ) => u == v || occurs u v₁
| u, v@(max v₁ v₂ ) => u == v || occurs u v₁ || occurs u v₂
| u, v@(imax v₁ v₂ ) => u == v || occurs u v₁ || occurs u v₂
| u, v => u == v
def ctorToNat : Level Nat
| zero .. => 0
| param .. => 1
| mvar .. => 2
| succ .. => 3
| max .. => 4
| imax .. => 5
def normLtAux : Level Nat Level Nat Bool
| succ l₁, k₁, l₂, k₂ => normLtAux l₁ (k₁+1) l₂ k₂
| l₁, k₁, succ l₂, k₂ => normLtAux l₁ k₁ l₂ (k₂+1)
| l₁@(max l₁₁ l₁₂), k₁, l₂@(max l₂₁ l₂₂), k₂ =>
if l₁ == l₂ then k₁ < k₂
else if l₁₁ != l₂₁ then normLtAux l₁₁ 0 l₂₁ 0
else normLtAux l₁₂ 0 l₂₂ 0
| l₁@(imax l₁₁ l₁₂), k₁, l₂@(imax l₂₁ l₂₂), k₂ =>
if l₁ == l₂ then k₁ < k₂
else if l₁₁ != l₂₁ then normLtAux l₁₁ 0 l₂₁ 0
else normLtAux l₁₂ 0 l₂₂ 0
| param n₁, k₁, param n₂, k₂ => if n₁ == n₂ then k₁ < k₂ else Name.lt n₁ n₂ -- use `Name.lt` because it is lexicographical
/-
We also use `Name.lt` in the following case to make sure universe parameters in a declaration
are not affected by shifted indices. We used to use `Name.quickLt` which is not stable over shifted indices (the hashcodes change),
and changes to the elaborator could affect the universe parameters and break code that relies on an explicit order.
Example: test `tests/lean/343.lean`.
-/
| mvar n₁, k₁, mvar n₂, k₂ => if n₁ == n₂ then k₁ < k₂ else Name.lt n₁.name n₂.name
| l₁, k₁, l₂, k₂ => if l₁ == l₂ then k₁ < k₂ else ctorToNat l₁ < ctorToNat l₂
/--
A total order on level expressions that has the following properties
- `succ l` is an immediate successor of `l`.
- `zero` is the minimal element.
This total order is used in the normalization procedure. -/
def normLt (l₁ l₂ : Level) : Bool :=
normLtAux l₁ 0 l₂ 0
def isAlreadyNormalizedCheap : Level Bool
| zero => true
| param _ => true
| mvar _ => true
| succ u => isAlreadyNormalizedCheap u
| _ => false
/- Auxiliary function used at `normalize` -/
private def mkIMaxAux : Level Level Level
| _, zero => zero
| zero, u => u
| succ zero, u => u
| u₁, u₂ => if u₁ == u₂ then u₁ else mkLevelIMax u₁ u₂
/- Auxiliary function used at `normalize` -/
@[specialize] private partial def getMaxArgsAux (normalize : Level Level) : Level Bool Array Level Array Level
| max l₁ l₂, alreadyNormalized, lvls => getMaxArgsAux normalize l₂ alreadyNormalized (getMaxArgsAux normalize l₁ alreadyNormalized lvls)
| l, false, lvls => getMaxArgsAux normalize (normalize l) true lvls
| l, true, lvls => lvls.push l
private def accMax (result : Level) (prev : Level) (offset : Nat) : Level :=
if result.isZero then prev.addOffset offset
else mkLevelMax result (prev.addOffset offset)
/- Auxiliary function used at `normalize`.
Remarks:
- `lvls` are sorted using `normLt`
- `extraK` is the outer offset of the `max` term. We will push it inside.
- `i` is the current array index
- `prev + prevK` is the "previous" level that has not been added to `result` yet.
- `result` is the accumulator
-/
private partial def mkMaxAux (lvls : Array Level) (extraK : Nat) (i : Nat) (prev : Level) (prevK : Nat) (result : Level) : Level :=
if h : i < lvls.size then
let lvl := lvls[i]
let curr := lvl.getLevelOffset
let currK := lvl.getOffset
if curr == prev then
mkMaxAux lvls extraK (i+1) curr currK result
else
mkMaxAux lvls extraK (i+1) curr currK (accMax result prev (extraK + prevK))
else
accMax result prev (extraK + prevK)
/-
Auxiliary function for `normalize`. It assumes `lvls` has been sorted using `normLt`.
It finds the first position that is not an explicit universe. -/
private partial def skipExplicit (lvls : Array Level) (i : Nat) : Nat :=
if h : i < lvls.size then
let lvl := lvls[i]
if lvl.getLevelOffset.isZero then skipExplicit lvls (i+1) else i
else
i
/--
Auxiliary function for `normalize`.
`maxExplicit` is the maximum explicit universe level at `lvls`.
Return true if it finds a level with offset ≥ maxExplicit.
`i` starts at the first non explicit level.
It assumes `lvls` has been sorted using `normLt`.
-/
private partial def isExplicitSubsumedAux (lvls : Array Level) (maxExplicit : Nat) (i : Nat) : Bool :=
if h : i < lvls.size then
let lvl := lvls[i]
if lvl.getOffset maxExplicit then true
else isExplicitSubsumedAux lvls maxExplicit (i+1)
else
false
/- Auxiliary function for `normalize`. See `isExplicitSubsumedAux` -/
private def isExplicitSubsumed (lvls : Array Level) (firstNonExplicit : Nat) : Bool :=
if firstNonExplicit == 0 then false
else
let max := lvls[firstNonExplicit - 1]!.getOffset
isExplicitSubsumedAux lvls max firstNonExplicit
partial def normalize (l : Level) : Level :=
if isAlreadyNormalizedCheap l then l
else
let k := l.getOffset
let u := l.getLevelOffset
match u with
| max l₁ l₂ =>
let lvls := getMaxArgsAux normalize l₁ false #[]
let lvls := getMaxArgsAux normalize l₂ false lvls
let lvls := lvls.qsort normLt
let firstNonExplicit := skipExplicit lvls 0
let i := if isExplicitSubsumed lvls firstNonExplicit then firstNonExplicit else firstNonExplicit - 1
let lvl₁ := lvls[i]!
let prev := lvl₁.getLevelOffset
let prevK := lvl₁.getOffset
mkMaxAux lvls k (i+1) prev prevK Level.zero
| imax l₁ l₂ =>
if l₂.isNeverZero then addOffset (normalize (mkLevelMax l₁ l₂)) k
else
let l₁ := normalize l₁
let l₂ := normalize l₂
addOffset (mkIMaxAux l₁ l₂) k
| _ => unreachable!
/--
Return true if `u` and `v` denote the same level.
Check is currently incomplete.
-/
def isEquiv (u v : Level) : Bool :=
u == v || u.normalize == v.normalize
/-- Reduce (if possible) universe level by 1 -/
def dec : Level Option Level
| zero => none
| param _ => none
| mvar _ => none
| succ l => l
| max l₁ l₂ => return mkLevelMax ( dec l₁) ( dec l₂)
/- Remark: `mkLevelMax` in the following line is not a typo.
If `dec l₂` succeeds, then `imax l₁ l₂` is equivalent to `max l₁ l₂`. -/
| imax l₁ l₂ => return mkLevelMax ( dec l₁) ( dec l₂)
/- Level to Format/Syntax -/
namespace PP
inductive Result where
| leaf : Name Result
| num : Nat Result
| offset : Result Nat Result
| maxNode : List Result Result
| imaxNode : List Result Result
def Result.succ : Result Result
| Result.offset f k => Result.offset f (k+1)
| Result.num k => Result.num (k+1)
| f => Result.offset f 1
def Result.max : Result Result Result
| f, Result.maxNode Fs => Result.maxNode (f::Fs)
| f₁, f₂ => Result.maxNode [f₁, f₂]
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 :=
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
| mvar n =>
if mvars then
Result.leaf <| n.name.replacePrefix `_uniq (Name.mkSimple "?u")
else
Result.leaf `_
private def parenIfFalse : Format Bool Format
| f, true => f
| f, false => f.paren
mutual
private partial def Result.formatLst : List Result Format
| [] => Format.nil
| r::rs => Format.line ++ format r false ++ formatLst rs
partial def Result.format : Result Bool Format
| Result.leaf n, _ => Std.format n
| Result.num k, _ => toString k
| 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
| Result.maxNode fs, r => parenIfFalse (Format.group <| "max" ++ formatLst fs) r
| Result.imaxNode fs, r => parenIfFalse (Format.group <| "imax" ++ formatLst fs) r
end
protected partial def Result.quote (r : Result) (prec : Nat) : Syntax.Level :=
let addParen (s : Syntax.Level) :=
if prec > 0 then Unhygienic.run `(level| ( $s )) else s
match r with
| Result.leaf n => Unhygienic.run `(level| $(mkIdent n):ident)
| Result.num k => Unhygienic.run `(level| $(quote k):num)
| Result.offset r 0 => Result.quote r prec
| Result.offset r (k+1) => addParen <| Unhygienic.run `(level| $(Result.quote r 65) + $(quote (k+1)):num)
| Result.maxNode rs => addParen <| Unhygienic.run `(level| max $(rs.toArray.map (Result.quote · max_prec))*)
| Result.imaxNode rs => addParen <| Unhygienic.run `(level| imax $(rs.toArray.map (Result.quote · max_prec))*)
end PP
protected def format (u : Level) (mvars : Bool) : Format :=
(PP.toResult u mvars).format true
instance : ToFormat Level where
format u := Level.format u (mvars := true)
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
instance : Quote Level `level where
quote := Level.quote
end Level
@[inline] private def mkLevelMaxCore (u v : Level) (elseK : Unit Level) : Level :=
let subsumes (u v : Level) : Bool :=
if v.isExplicit && u.getOffset v.getOffset then true
else match u with
| Level.max u₁ u₂ => v == u₁ || v == u₂
| _ => false
if u == v then u
else if u.isZero then v
else if v.isZero then u
else if subsumes u v then u
else if subsumes v u then v
else if u.getLevelOffset == v.getLevelOffset then
if u.getOffset v.getOffset then u else v
else
elseK ()
/-- Similar to `mkLevelMax`, but applies cheap simplifications -/
def mkLevelMax' (u v : Level) : Level :=
mkLevelMaxCore u v fun _ => mkLevelMax u v
def simpLevelMax' (u v : Level) (d : Level) : Level :=
mkLevelMaxCore u v fun _ => d
@[inline] private def mkLevelIMaxCore (u v : Level) (elseK : Unit Level) : Level :=
if v.isNeverZero then mkLevelMax' u v
else if v.isZero then v
else if u.isZero then v
else if u == v then u
else elseK ()
/-- Similar to `mkLevelIMax`, but applies cheap simplifications -/
def mkLevelIMax' (u v : Level) : Level :=
mkLevelIMaxCore u v fun _ => mkLevelIMax u v
def simpLevelIMax' (u v : Level) (d : Level) :=
mkLevelIMaxCore u v fun _ => d
namespace Level
/-!
The update functions try to avoid allocating new values using pointer equality.
Note that if the `update*!` functions are used under a match-expression,
the compiler will eliminate the double-match.
-/
@[inline] private unsafe def updateSucc!Impl (lvl : Level) (newLvl : Level) : Level :=
match lvl with
| succ l => if ptrEq l newLvl then lvl else mkLevelSucc newLvl
| _ => panic! "succ level expected"
@[implemented_by updateSucc!Impl]
def updateSucc! (lvl : Level) (newLvl : Level) : Level :=
match lvl with
| succ _ => mkLevelSucc newLvl
| _ => panic! "succ level expected"
@[inline] private unsafe def updateMax!Impl (lvl : Level) (newLhs : Level) (newRhs : Level) : Level :=
match lvl with
| max lhs rhs => if ptrEq lhs newLhs && ptrEq rhs newRhs then simpLevelMax' newLhs newRhs lvl else mkLevelMax' newLhs newRhs
| _ => panic! "max level expected"
@[implemented_by updateMax!Impl]
def updateMax! (lvl : Level) (newLhs : Level) (newRhs : Level) : Level :=
match lvl with
| max _ _ => mkLevelMax' newLhs newRhs
| _ => panic! "max level expected"
@[inline] private unsafe def updateIMax!Impl (lvl : Level) (newLhs : Level) (newRhs : Level) : Level :=
match lvl with
| imax lhs rhs => if ptrEq lhs newLhs && ptrEq rhs newRhs then simpLevelIMax' newLhs newRhs lvl else mkLevelIMax' newLhs newRhs
| _ => panic! "imax level expected"
@[implemented_by updateIMax!Impl]
def updateIMax! (lvl : Level) (newLhs : Level) (newRhs : Level) : Level :=
match lvl with
| imax _ _ => mkLevelIMax' newLhs newRhs
| _ => panic! "imax level expected"
def mkNaryMax : List Level Level
| [] => Level.zero
| [u] => u
| u::us => mkLevelMax' u (mkNaryMax us)
@[specialize] def substParams (u : Level) (s : Name Option Level) : Level :=
go u
where
go (u : Level) : Level :=
match u with
| .zero => u
| .succ v => if u.hasParam then u.updateSucc! (go v) else u
| .max v₁ v₂ => if u.hasParam then u.updateMax! (go v₁) (go v₂) else u
| .imax v₁ v₂ => if u.hasParam then u.updateIMax! (go v₁) (go v₂) else u
| .param n => match s n with
| some u' => u'
| none => u
| u => u
def getParamSubst : List Name List Level Name Option Level
| p::ps, u::us, p' => if p == p' then some u else getParamSubst ps us p'
| _, _, _ => none
def instantiateParams (u : Level) (paramNames : List Name) (vs : List Level) : Level :=
u.substParams (getParamSubst paramNames vs)
def geq (u v : Level) : Bool :=
go u.normalize v.normalize
where
go (u v : Level) : Bool :=
u == v ||
let k := fun () =>
match v with
| imax v₁ v₂ => go u v₁ && go u v₂
| _ =>
let v' := v.getLevelOffset
(u.getLevelOffset == v' || v'.isZero)
&& u.getOffset v.getOffset
match u, v with
| _, zero => true
| u, max v₁ v₂ => go u v₁ && go u v₂
| max u₁ u₂, v => go u₁ v || go u₂ v || k ()
| imax _ u₂, v => go u₂ v
| succ u, succ v => go u v
| _, _ => k ()
termination_by (u, v)
end Level
abbrev LevelMap (α : Type) := Std.HashMap Level α
abbrev PersistentLevelMap (α : Type) := PHashMap Level α
abbrev LevelSet := Std.HashSet Level
abbrev PersistentLevelSet := PHashSet Level
abbrev PLevelSet := PersistentLevelSet
def Level.collectMVars (u : Level) (s : LMVarIdSet := {}) : LMVarIdSet :=
match u with
| succ v => collectMVars v s
| max u v => collectMVars u (collectMVars v s)
| imax u v => collectMVars u (collectMVars v s)
| mvar n => s.insert n
| _ => s
def Level.find? (u : Level) (p : Level Bool) : Option Level :=
let rec visit (u : Level) : Option Level :=
if p u then
return u
else match u with
| succ v => visit v
| max u v => visit u <|> visit v
| imax u v => visit u <|> visit v
| _ => failure
visit u
def Level.any (u : Level) (p : Level Bool) : Bool :=
u.find? p |>.isSome
end Lean
abbrev Nat.toLevel (n : Nat) : Lean.Level :=
Lean.Level.ofNat n