Zulip Chat Archive

Stream: lean4

Topic: Better error about uninstantiated universe variables


Jakub Nowak (Dec 16 2025 at 08:17):

The error message is unhelpful. The fix is to explicitly provide universe variable for Ordinal. Either by using Ordinal.{0} in definition of Foo.ordinalLevel, or Foo.ordinalLevel.{0} at call site. Is it possible to have a better error message in this case?

import Mathlib.SetTheory.Ordinal.Basic
import Mathlib.SetTheory.Ordinal.Family

inductive Foo (α : Type) : Type where
| base (k : Nat) : Foo α
| foo (f : α  Foo α) : Foo α

@[simp]
noncomputable def Foo.ordinalLevel : Foo α  Ordinal
| base k => k
| foo f =>  a, (f a).ordinalLevel + 1

/--
error: (kernel) declaration has metavariables 'Foo.sum'
---
error: (kernel) application type mismatch
  failed to pretty print expression (use 'set_option pp.rawOnError true' for raw representation)
argument has type
  failed to pretty print expression (use 'set_option pp.rawOnError true' for raw representation)
but function has type
  failed to pretty print expression (use 'set_option pp.rawOnError true' for raw representation)
-/
#guard_msgs in
def Foo.sum [Inhabited α] : Foo α  Nat
| base k => k
| foo f => Foo.sum (f default)
termination_by foo => Foo.ordinalLevel foo
decreasing_by
  simp_wf
  calc (f default).ordinalLevel
    _ < (f default).ordinalLevel + 1 := Order.lt_succ _
    _   a, (f a).ordinalLevel + 1 := Ordinal.le_iSup ..

Aaron Liu (Dec 16 2025 at 11:09):

does the termination checker not also check for universe mvars

Jakub Nowak (Dec 16 2025 at 23:13):

Was that a question? I don't know the answer, because I didn't understood the question. In what way universe mvars have to be checked?

Aaron Liu (Dec 16 2025 at 23:17):

I think this is probably the reason for why you get such an error, is that the termination checker does not process unassigned universe metavariables

Jakub Nowak (Dec 16 2025 at 23:24):

You think it should automatically default universe mvars that were left unsolved, to lowest possible?

Jakub Nowak (Dec 16 2025 at 23:28):

Is there an example of Lean doing this in some other context?

Aaron Liu (Dec 16 2025 at 23:40):

I think it should throw an error actually

Aaron Liu (Dec 16 2025 at 23:41):

but with a better error message of course

Jakub Nowak (Dec 17 2025 at 00:52):

Defaulting universe variables to lowest possible makes sense I think? I don't think setting universe variables lower can change solvable goal to unsolvable one? In worst case proof might require some ULift's.

Aaron Liu (Dec 17 2025 at 00:54):

it's done for definitions, for obvious reasons

Aaron Liu (Dec 17 2025 at 00:54):

and it's done for proofs because proofs are handled the same as definitions

Jakub Nowak (Dec 17 2025 at 00:55):

I think for definitions the unsolved universe variables are put as universe variables of the definition?

Aaron Liu (Dec 17 2025 at 00:55):

...right

Aaron Liu (Dec 17 2025 at 00:55):

yeah

Jakub Nowak (Dec 17 2025 at 00:56):

But I don't think it would make sense to put universe variables there are only used in the termination proof as part of the definition.

Aaron Liu (Dec 17 2025 at 00:56):

how does this work again?

Aaron Liu (Dec 17 2025 at 00:56):

no wait that's not right

Aaron Liu (Dec 17 2025 at 00:56):

wait no

Jakub Nowak (Dec 17 2025 at 00:56):

Jakub Nowak said:

I don't think setting universe variables lower can change solvable goal to unsolvable one?

Hmm, or maybe it actually could make a goal unsolvable? Because there's a significant difference between Sort 0 and Sort 1.

Aaron Liu (Dec 17 2025 at 00:57):

I remember there was a behavior where if the universe is mentioned but it isn't used anywhere then it throws an error

Aaron Liu (Dec 17 2025 at 00:57):

but that's not about metavariables

Aaron Liu (Dec 17 2025 at 00:57):

and there is also the behavior that if in a theorem you leave a universe metavariable then it throws an error

Aaron Liu (Dec 17 2025 at 00:58):

Jakub Nowak said:

Defaulting universe variables to lowest possible makes sense I think? I don't think setting universe variables lower can change solvable goal to unsolvable one? In worst case proof might require some ULift's.

as an example take docs#UCompactlyGeneratedSpace

Jakub Nowak (Dec 17 2025 at 00:58):

Jakub Nowak said:

I think for definitions the unsolved universe variables are put as universe variables of the definition?

Like, in the case of Foo.universeLevel Lean made this signature: def Foo.ordinalLevel.{u_1} : {α : Type} → Foo α → Ordinal.{u_1}. So it took the universe mvar that came from Ordinal and put it as a universe variable of the Foo.ordinalLevel.

Aaron Liu (Dec 17 2025 at 00:59):

but you mentioned it in the statement so that's a bit different

Aaron Liu (Dec 17 2025 at 00:59):

I think it also lifts it out if you mention it in the body of a definition?

Aaron Liu (Dec 17 2025 at 00:59):

but not the body of a proof...

Jakub Nowak (Dec 17 2025 at 01:08):

Jakub Nowak said:

Is there an example of Lean doing this in some other context?

Not exactly the same, but I can write

inductive Foo where
| mk (o : Ordinal.{3})

and Lean will pick Foo : Type 4, even though it could do Foo : Type (max u 4).
So in this case Lean automatically picks the lowest.
And, if I write

inductive Foo where
| nil

then it will pick Foo : Sort 1, not Foo : Sort 0. So, in this case it doesn't pick lowest possible, but it picks lowest possible that doesn't introduce Prop.
Maybe similar behaviour would make sense for automatically instantiating universe variables in proofs?

Aaron Liu (Dec 17 2025 at 01:12):

that's inductive types

Aaron Liu (Dec 17 2025 at 01:12):

I feel like that's different

Jakub Nowak (Dec 17 2025 at 01:12):

That is different.

Kyle Miller (Dec 17 2025 at 16:11):

inductive has a heuristic that if it's got only one-constructor, and that constructor has at least one field, and all fields are propositions, then it will chose Prop. That's because in this case it's a syntactic subsingleton and the kernel will generate a recursor that can eliminate into any universe. Plus, empirically people want an inductive predicate in this case (at least for structure).

inductive Foo (a : Nat) where
| nil (h : a = 1)
#check Foo -- Foo (a : Nat) : Prop

Otherwise it goes for Type _.

The inductive command doesn't have (good) heuristics for universe level metavariables inside constructors. Currently it does different things depending on whether or not the universe for the inductive was provided. It can also do some crazy things like

inductive Foo : Type where
| nil {α} (x : α)
#check Foo.nil -- Foo.nil {α : Prop} (x : α) : Foo

(It reasons "well, the only level that works here is 0, so go with that". I think this feature is my fault, probably from lean4#6125.)

Kyle Miller (Dec 17 2025 at 16:13):

I've got your first message starred by the way, since it's a bug if the error is being reported from the kernel. The elaborator is responsible for reporting metavariable errors and localizing them in the source code.

Kyle Miller (Dec 17 2025 at 16:20):

By the way, the elaborator does assign level metavariables to 0 when it reports errors, as an error-recovery mechanism.

It seems plausible that inside theorems, the elaborator could simply set them to 0 and not report anything — there shouldn't be any Prop/Type _ issues, since the elaborator checks whether levels can/can't be zero as it elaborates. My primary concern is that if a proof has metavariables then it might be brittle.


Last updated: Dec 20 2025 at 21:32 UTC