HepLean Documentation

Mathlib.Tactic.Linter.FlexibleLinter

The "flexible" linter #

The "flexible" linter makes sure that a "rigid" tactic (such as rw) does not act on the output of a "flexible" tactic (such as simp).

For example, this ensures that, if you want to use simp [...] in the middle of a proof, then you should replace simp [...] by one of

Otherwise, the linter will complain.

Simplifying and appealing to a geometric intuition, you can imagine a (tactic) proof like a directed graph, where

What the linter does is keeping track of nodes that are connected by flexible tactics and makes sure that only flexible or stoppers follow them. Such nodes are Stained. Whenever it reaches a stopper edge, the target node is no longer Stained and it is available again to rigid tactics.

Currently, the only tactics that "start" the bookkeeping are most forms of non-only simps. These are encoded by the flexible? predicate. Future modifications of the linter may increase the scope of the flexible? predicate and forbid a wider range of combinations.

TODO #

The example

example (h : 0 = 0) : True := by
  simp at h
  assumption

should trigger the linter, since assumption uses h that has been "stained" by simp at h. However, assumption contains no syntax information for the location h, so the linter in its current form does not catch this.

Implementation notes #

A large part of the code is devoted to tracking FVars and MVars between tactics.

For the FVars, this follows the following heuristic:

For the MVars, we use the information of Lean.Elab.TacticInfo.goalsBefore and Lean.Elab.TacticInfo.goalsAfter. By looking at the mvars that are either only "before" or only "after", we focus on the "active" goals. We then propagate all the FVarIds that were present in the "before" goals to the "after" goals, while leaving untouched the ones in the "inert" goals.

The flexible linter makes sure that "rigid" tactics do not follow "flexible" tactics.

flexible? stx is true if stx is syntax for a tactic that takes a "wide" variety of inputs and modifies them in possibly unpredictable ways.

The prototypical flexible tactic is simp. The prototypical non-flexible tactic rw. simp only is also non-flexible.

Instances For

    Heuristics for determining goals goals that a tactic modifies what they become #

    The two definitions goalsTargetedBy, goalsCreatedBy extract a list of MVarIds attempting to determine on which goals the tactic t is acting and what are the resulting modified goals. This is mostly based on the heuristic that the tactic will "change" an MVarId.

    goalsTargetedBy t are the MVarIds before the TacticInfo t that "disappear" after it. They should correspond to the goals in which the tactic t performs some action.

    Equations
    Instances For

      goalsCreatedBy t are the MVarIds after the TacticInfo t that were not present before. They should correspond to the goals created or changed by the tactic t.

      Equations
      Instances For

        extractCtxAndGoals take? tree takes as input a function take? : Syntax → Bool and an InfoTree and returns the array of pairs (stx, mvars), where stx is a syntax node such that take? stx is true and mvars indicates the goal state:

        • the context before stx
        • the context after stx
        • a list of metavariables closed by stx
        • a list of metavariables created by stx

        A typical usage is to find the goals following a simp application.

        Stained is the type of the stained locations: it can be

        • a Name (typically of associated to the FVarId of a local declaration);
        • the goal ();
        • the "wildcard" -- all the declaration in context (*).
        Instances For

          Converting a Stained to a String:

          • a Name is represented by the corresponding string;
          • goal is represented by ;
          • wildcard is represented by *.
          Equations
          • One or more equations did not get rendered due to their size.

          toStained stx scans the input Syntax stx extracting identifiers and atoms, making an effort to convert them to Stained. The function is used to extract "location" information about stx: either explicit locations as in rw [] at locations or implicit ones as rw [h].

          Whether or not what this function extracts really is a location will be determined by the linter using data embedded in the InfoTrees.

          getStained stx expects stx to be an argument of a node of SyntaxNodeKind Lean.Parser.Tactic.location. Typically, we apply getStained to the output of getLocs.

          See getStained! for a similar function.

          getStained! stx expects stx to be an argument of a node of SyntaxNodeKind Lean.Parser.Tactic.location. Typically, we apply getStained! to the output of getLocs.

          It returns the HashSet of Stained determined by the locations in stx.

          The only difference with getStained stx, is that getStained! never returns {}: if getStained stx = {}, then getStained' stx = {.goal}.

          This means that tactics that do not have an explicit "at" in their syntax will be treated as acting on the main goal.

          Equations
          • One or more equations did not get rendered due to their size.
          Instances For

            Stained.toFMVarId mv lctx st takes a metavariable mv, a local context lctx and a Stained st and returns the array of pairs (FVarId, mv)s that lctx assigns to st (the second component is always mv):

            • if st "is" a Name, returns the singleton of the FVarId with the name carried by st;
            • if st is .goal, returns the singleton #[default];
            • if st is .wildcard, returns the array of all the FVarIds in lctx with also default (to keep track of the goal).
            Equations
            Instances For

              SyntaxNodeKinds that are mostly "formatting": mostly they are ignored because we do not want the linter to spend time on them. The nodes that they contain will be visited by the linter anyway. The nodes that follow them, though, will not be visited by the linter.

              Equations
              • One or more equations did not get rendered due to their size.
              Instances For

                SyntaxNodeKinds that are allowed to follow a flexible tactic: simp, simp_all, simpa, dsimp, constructor, congr, done, rfl, omega, abel, ring, linarith, nlinarith, norm_cast, aesop, tauto, fun_prop, split, split_ifs.

                Equations
                • One or more equations did not get rendered due to their size.
                Instances For

                  By default, if a SyntaxNodeKind is not special-cased here, then the linter assumes that the tactic will use the goal as well: this heuristic works well with exact, refine, apply. For tactics such as cases this is not true: for these tactics, usesGoal? yields `false.

                  Instances For

                    getFVarIdCandidates fv name lctx takes an input an FVarId, a Name and a LocalContext. It returns an array of guesses for a "best fit" FVarId in the given LocalContext. The first entry of the array is the input FVarId fv, if it is present. The next entry of the array is the FVarId with the given Name, if present.

                    Usually, the first entry of the returned array is "the best approximation" to (fv, name).

                    Equations
                    Instances For

                      Tactics often change the name of the current MVarId, as well as the names of the FVarIds appearing in their local contexts. The function reallyPersist makes an attempt at "tracking" pairs (fvar, mvar) across a simultaneous change represented by an "old" list of MVarIds and the corresponding MetavarContext and a new one.

                      This arises in the context of the information encoded in the InfoTrees when processing a tactic proof.

                      persistFVars is one step in persisting: track a single FVarId between two LocalContexts. If an FVarId with the same unique name exists in the new context, use it. Otherwise, if an FVarId with the same userName exists in the new context, use it. If both of these fail, return default (i.e. "fail").

                      Equations
                      Instances For

                        reallyPersist converts an array of pairs (fvar, mvar) to another array of the same type.

                        Equations
                        • One or more equations did not get rendered due to their size.
                        Instances For

                          The main implementation of the flexible linter.

                          Equations
                          • One or more equations did not get rendered due to their size.
                          Instances For