Zulip Chat Archive

Stream: lean4

Topic: hasLooseBVar misunderstanding?


Sebastian Reichelt (Jun 20 2021 at 15:54):

I'm trying to write my first Lean tactic (in Lean 4) and mostly just guessing a lot. So I'm not sure if "loose bvar" just means something different than I had guessed, or if I've stumbled upon a bug, or if I've built a term that is inconsistent in some way.

I would like to check if a lambda expression is constant in the sense that its body may reference anything except its own variable. Am I correctly assuming that I can check this with !b.hasLooseBVar 0, where b is the body? If not, what is the best way to do it?

The problem: Within my tactic, I have produced a term where hasLooseBVar 0 returns false, yet somewhere in an argument of an argument, the term contains a reference to bvar 0. When I print it, I see "#0" there as expected. The term does not itself contain any lambda abstractions, only applications, which hopefully rules out indexing issues. In case it matters, the term also contains a reference to bvar 1, and hasLooseBVar 1 does return true.

If necessary, I can try to create an MWE, but I'd like someone more knowledgeable to check whether my guesses are correct first. Thanks!

Sebastian Ullrich (Jun 20 2021 at 16:03):

Your intuition sounds about right, yes. Lean itself uses hasLooseBVar 0 in that manner, see e.g. Expr.eta.

Sebastian Reichelt (Jun 20 2021 at 18:21):

OK, I've managed to create a self-contained example. I'm sure it can still be minimized a lot.
While isolating the problem, I found that instantiateMVars fixes it. So now I'm unsure whether it is a Lean issue or not. The error message in the example below is still a bit unexpected.

import Lean

open Lean Expr Meta Elab Tactic

def swapLambdaParams (e : Expr) : MetaM Expr :=
  match e with
  | lam x X t _ => do
    let f  mkFreshExprMVar none;
    let g  mkFreshExprMVar none;
    if ( isDefEq t (mkApp f g)) then
      -- Uncommenting this line fixes the problem.
      --let g ← instantiateMVars g;
      let b := headBeta (mkApp2 (mkLambda x BinderInfo.default X g) (mkBVar 0) (mkBVar 1));
      unless b.hasLooseBVar 0 do
        throwError m!"{b} unexpectedly does not have any bvar 0"
      mkLambda "y" BinderInfo.default X (mkApp f (mkLambda x BinderInfo.default X b))
    else
      throwError "not defeq"
  | _ => throwError "not a lambda"

syntax (name := test) "test " term : tactic

@[tactic test] def Tactic.evalTest : Tactic := fun stx =>
  match stx with
  | `(tactic| test $e) => do
    let e'  elabTerm e none;
    liftMetaTactic fun mvarId => do
      withMVarContext mvarId do
        checkNotAssigned mvarId `test
        let r  swapLambdaParams e';
        assignExprMVar mvarId r;
        []
  | _ => throwUnsupportedSyntax

def f (g : Nat  Nat) := g 42
-- See error message.
def h : Nat  Nat := by test (λ x : Nat => f (λ y => x + y))

Sebastian Ullrich (Jun 20 2021 at 18:23):

Yes, hasLooseBVar does not see through assigned but not substituted metavariables. It cannot, as a pure function.


Last updated: Dec 20 2023 at 11:08 UTC