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.

Equations
    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

                  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
                    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
                            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
                                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.

                                  Equations
                                    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
                                                Instances For

                                                  The main implementation of the flexible linter.

                                                  Equations
                                                    Instances For