Zulip Chat Archive

Stream: lean4

Topic: meta variable confusion


Joachim Breitner (Aug 27 2023 at 08:52):

@Jireh Loreaux reported a bug with the new #find command. Here is a self-contained reproducer,
It prints (true, false). Why does the second one fail? I assume I am doing something wrong with the metavariables, but what?

import Mathlib.Tactic.Basic
import Mathlib.Tactic.RunCmd

set_option autoImplicit false
-- set_option pp.raw false

open Lean Meta Elab Command

class Star (R : Type _) where star : R  R
export Star(star)
-- Without an instance, `#check (star _)` will always fail
instance : Star Unit where star := fun () => ()
theorem star_comm_self' {R} [Mul R] [Star R] (x : R) : star x * x = x * star x := sorry

def ConstMatcher := ConstantInfo  MetaM Bool

def matchAnywhere (t : Expr) : MetaM ConstMatcher := withReducible do
  let head := t.toHeadIndex
  let head_args := t.headNumArgs
  let pat  abstractMVars ( instantiateMVars t)
  -- dbg_trace (pat.expr)
  pure fun (c : ConstantInfo) => withReducible do
    let found   IO.mkRef false
    let cTy := c.instantiateTypeLevelParams ( mkFreshLevelMVars c.numLevelParams)
    Lean.Meta.forEachExpr' cTy fun sub_e => do
      if head == sub_e.toHeadIndex && head_args == sub_e.headNumArgs then do
        withNewMCtxDepth $ do
          let pat := pat.expr.instantiateLevelParamsArray pat.paramNames
            ( mkFreshLevelMVars pat.numMVars).toArray
          let (_, _, pat)  lambdaMetaTelescope pat
          -- dbg_trace (pat, sub_e)
          Lean.logInfo m!"{pat}\n{sub_e}"
          if  isDefEq pat sub_e
          then found.set true
      not <$> found.get
    found.get

run_cmd liftTermElabM do
  let s1  `(term|star ?a * ?a = ?a * star ?_)
  let t1  Lean.Elab.Term.elabTerm s1 none
  let matcher1  matchAnywhere t1

  let s2  `(term|star ?a * ?a = ?a * star ?a)
  let t2  Lean.Elab.Term.elabTerm s2 none
  let matcher2  matchAnywhere t2

  let ci := ( getEnv).constants.find! ``star_comm_self'
  dbg_trace ( matcher1 ci,  matcher2 ci)

Mario Carneiro (Aug 27 2023 at 09:16):

this line is wrong:

          let pat := pat.expr.instantiateLevelParamsArray pat.paramNames
            ( mkFreshLevelMVars pat.numMVars).toArray

pat.numMVars is the number of expr metavariables abstracted (which you should be passing to lambdaMetaTelescope), while the number of universe metavariables is pat.paramNames.size

Joachim Breitner (Aug 27 2023 at 09:19):

Spot on, thanks!

Joachim Breitner (Aug 27 2023 at 09:21):

I guess I could just write

          let (_, _, pat)  openAbstractMVarsResult pat

instead

Kyle Miller (Aug 27 2023 at 12:13):

@Joachim Breitner Here's a way to elaborate the term while allowing typeclass failures. That lets you delete instance : Star Unit where star := fun () => ().

open Lean.Elab in
def elabTerm' (t : Term) (expectedType? : Option Expr) : TermElabM Expr := do
  withTheReader Term.Context (fun ctx => { ctx with ignoreTCFailures := true }) do
    let t  Term.elabTerm t expectedType?
    Term.synthesizeSyntheticMVars (mayPostpone := false) (ignoreStuckTC := true)
    return t

Joachim Breitner (Aug 27 2023 at 12:26):

Thanks! ignoreTcFailures looks scary … what kind of errors would I not catch this way?

Joachim Breitner (Aug 27 2023 at 12:28):

And why does the presence of an instance which does not even apply make a difference to the elaborator in the first place? This is not just my code, it’s even

class Star (R : Type _) where star : R  R
-- instance : Star Unit where star := fun () => ()
#check (Star.star _)

where I didn’t expect this to fail.

Kyle Miller (Aug 27 2023 at 12:38):

It fails because when it elaborates Star.star _, it triggers typeclass inference to fill in the instance argument, and it quickly detects that this problem will never have a solution (there are no instances), so it fails.

Kyle Miller (Aug 27 2023 at 12:39):

ignoreTCFailures is used when elaborating patterns in match expressions for example. You don't care if there are actually any instances out there when you're elaborating a pattern.

Joachim Breitner (Aug 27 2023 at 12:54):

Hmm, it seems strange to rely on the absence of type class instances, due to their open world nature. But it’s probably a corner case that doesn’t matter a lot. I’ll copy your elabTerm' into the #find implementation.

Kyle Miller (Aug 27 2023 at 13:04):

When you're elaborating a term, it switches to a closed world assumption -- no one is going to add new instances while a term is being elaborated.

Kyle Miller (Aug 27 2023 at 13:20):

I didn't mention that the concrete consequence of these options is that all the instance argument metavariables become plain metavariables when they fail to be synthesized. That seems to be what you want for a pattern.

Joachim Breitner (Aug 29 2023 at 08:59):

I just pushed a failing test to the joachim/find branch, where

#assert_match List.map (?a -> ?b) -> List ?a -> List ?b

doesn’t match (using the #find machinery), at the end of test/Find.lean – if someone wants to have a look why.

Oddly,

#assert_match List.map |- (?a -> ?b) -> List ?a -> List ?b

using the “conclusion matcher” rather than the “everywhere matcher” works.

Mario Carneiro (Aug 29 2023 at 11:07):

isn't that a parsing issue? you just asked to find something of the form ((List.map (?a -> ?b)) -> List ?a -> List ?b)

Mario Carneiro (Aug 29 2023 at 11:07):

i.e. you got bit by the parsing problem I had but in reverse

Joachim Breitner (Aug 29 2023 at 12:34):

No, the #assert_match is an ad-hoc command I created for the test, takes an ident, an optional turnstile, and an expression. Probably I should add a mandatory matches keyword there, for clarity :-)

Joachim Breitner (Sep 02 2023 at 21:21):

I found the problem: docs#Lean.Meta.forEachExpr' did not quite work as I thought it does: Given a type a → b → c, it does not visit b → c. Now (not yet deployed, have to rebuild mathlib…) List.map is found as it should.
(And that every deployment takes 3h because I rebuild all of mathlib using the nix setup is a bit silly…)


Last updated: Dec 20 2023 at 11:08 UTC