Documentation

GeneralizationLinter.Analysis.Verify

Verification #

This module concerns itself with elaborating a weakened term (usually the proof term of a theorem) and type-checking the result against some expected type (usually the conclusion of a theorem). This is used to verify a weakening candidate before it is ever suggested. The goal is to never emit a single false positive, i.e., an erroneous suggestion.

Run act quietly. When we run a verification gate, candidates that are rejected would emit errors that we don't want to bother the user with.


Implementation notes

We turn off Elab.async because asynchronicity would make it impossible for us to ensure we capture the emitted messages.

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

          Returns weakening suggestion candidates for a given declaration. These are unverified, except that, when config.redundancyGuard and config.verify are both set, candidates reshaped into drops by reshapeRedundantToDrops have been verified (and removed if they failed).

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

            Recompile #

            • binders : Bool

              true if no other binder mentions the weakened binder by name, false if one does. Note that this is a syntactic scan (bindersMention), not a re-elaboration: it does not see a binder whose type the elaborator filled in from the weakened one.

            • concl : Bool

              true if the declaration's conclusion would not have to be modified after applying the weakening, false if it might. A false records only a failure to confirm: re-elaboration failed, or the conclusion's source syntax was unavailable.

            • body : Bool

              true if the declaration's body (i.e., value) wouldn't have to be modified after applying the weakening, false if it might. As for concl, a false is only a failure to confirm.

            Instances For
              Equations
              Instances For

                Some weakenings may be genuine weakenings, but require modifications in the binders', conclusion's, or value's (i.e., proof term's) source code. We specify which parts don't need modifications and which parts do (or might) in the parts argument passed to the .holds "grade".

                If verification is turned off, the .unverified "grade" is assigned instead.

                Instances For

                  Source code (Syntax) of a declaration and its value.

                  Instances For

                    Given a weakened declaration W of the form ‹binders› : concl (where concl is the same as the original declaration's), re-elaborate the declaration's value's source code (src.body, usually corresponding to a proof term) into val and type-check that we have val : concl. Return some val if successful, or none otherwise.

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

                      Check that re-elaborated conclusion is definitionally equal to the old conclusion.


                      Implementation notes

                      forallTelescope W strips every from W, even ones that may actually belong to the conclusion of W, which we don't want. So we consult conclStx and check how many leading s it has, and re-abstract precisely that number of the telescoped W's trailing binders.

                      For example, if the declaration's signature were [Group α] (a b : α) : ∀ (c : α), a = b → b = c → a = c and Group α got weakened to Monoid α, then W would be that signature with [Monoid α] in place of [Group α], so args would be #[α, inst, a, b, c, t₁, t₂], where inst : Monoid α, t₁ : a = b, and t₂ : b = c would in truth be dynamically-generated hygienic binder names, and shortConcl would be a = c. Meanwhile, conclStx = ‹∀ (c : α), a = b → b = c → a = c›, so it can tell us that the "real" conclusion actually has 3 leading s (corresponding to c, t₁, and t₂).

                      Now, newConcl = ‹∀ (c : α), a = b → b = c → a = c› (note that the ‹› brackets are being used both for Syntax and Exprs). We now want to check that newConcl matches the old conclusion, but only have the shortConcl version of the old conclusion. Hence, we extract the last 3 arguments (we know it's 3 because that's how many leading s conclStx has) from args (which were telescoped from W) and build a forall-expression, using them as the arguments and shortConcl as the conclusion, which yields ∀ (c : α) (t₁ : a = b) (t₂ : b = c), a = c. We then check whether this "full" old conclusion Expr is definitionally equal to newConcl, and receive a verdict that is informative to us, and not simply false because of a mismatch in -arity of the expressions.

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

                        Does any binder in binders, as source code (Syntax), mention the binder name?


                        Examples

                        Suppose we have

                        theorem foo {R M : Type*}
                          [inst₁ : Ring R]
                          [inst₂ : AddCommMonoid M]
                          [inst₃ : @Module R M (@Ring.toSemiring R inst₁) inst₂] : … := …
                        

                        The typical use case for bindersMention would be checking whether weakening [inst₁ : Ring R] to [inst₁ : Semiring R] would require the user to modify foo's other binders in any way. To check this, we would call bindersMention #[‹[inst₂ : AddCommMonoid M]›, ‹[inst₃ : @Module R M (@Ring.toSemiring R inst₁) inst₂]›] `inst₁ and get true.

                        Had we instead had the following

                        theorem foo {R M : Type*}
                          [inst₁ : Ring R]
                          [inst₂ : AddCommMonoid M]
                          [inst₃ : Module R M] : … := …
                        

                        then we'd have called bindersMention #[‹[inst₂ : AddCommMonoid M]›, ‹[inst₃ : Module R M]›] `inst₁ and gotten false.

                        bindersMention #[‹[inst₁ : Ring R]›] `R = true
                        bindersMention #[‹[inst₁ : Ring R]›] `inst₁ = false
                        bindersMention #[‹{R M : Type*}›] `R = false
                        

                        Implementation notes

                        For reference:

                        [inst₃ : @Module R M (@Ring.toSemiring R inst₁) inst₂]    [Module R M]
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^    ^^^^^^^^^^^^
                        `Lean.Parser.Term.instBinder`                             `Lean.Parser.Term.instBinder`
                        ├─ [0] `atom "["`                                         ├─ [0] `atom "["`
                        ├─ [1] `null`                                             ├─ [1] `null`
                        │  ├─ [0] `ident inst₃`                                   ├─ [2] `Lean.Parser.Term.app`
                        │  └─ [1] `atom ":"`                                      │  ├─ [0] `ident Module`
                        ├─ [2] `Lean.Parser.Term.app`                             │  └─ [1] `null`
                        │  ├─ [0] `Lean.Parser.Term.explicit`                     │     ├─ [0] `ident R`
                        │  │  ├─ [0] `atom "@"`                                   │     └─ [1] `ident M`
                        │  │  └─ [1] `ident Module`                               └─ [3] `atom "]"`
                        │  └─ [1] `null`
                        │     ├─ [0] `ident R`
                        │     ├─ [1] `ident M`
                        │     ├─ [2] `Lean.Parser.Term.paren`
                        │     │  ├─ [0] `Lean.Parser.Term.hygienicLParen`
                        │     │  │  ├─ [0] `atom "("`
                        │     │  │  └─ [1] `hygieneInfo`
                        │     │  │     └─ [0] `ident [anonymous]`
                        │     │  ├─ [1] `Lean.Parser.Term.app`
                        │     │  │  ├─ [0] `Lean.Parser.Term.explicit`
                        │     │  │  │  ├─ [0] `atom "@"`
                        │     │  │  │  └─ [1] `ident Ring.toSemiring`
                        │     │  │  └─ [1] `null`
                        │     │  │     ├─ [0] `ident R`
                        │     │  │     └─ [1] `ident inst₁`
                        │     │  └─ [2] `atom ")"`
                        │     └─ [3] `ident inst₂`
                        └─ [3] `atom "]"`
                        
                        Equations
                        • One or more equations did not get rendered due to their size.
                        Instances For

                          Run x with maxHeartbeats set to the lesser of budget and the ambient maxHeartbeats, treating an ambient value of 0 ("unlimited") as larger than any budget. In either case x runs under withCurrHeartbeats, so it gets a fresh allowance of that many heartbeats rather than whatever is left of the ambient one.

                          If a runtime (or other non-interrupt) exception occurs while running x, it is caught and dflt ("default") is returned.

                          If budget is 0, runs x directly without any restrictions or exception handling.


                          Examples

                          -- ambient `maxHeartbeats 200_000`, i.e. `Core.Context.maxHeartbeats = 200_000_000`
                          withHeartbeatBudget 1_000 dflt x             -- `x` gets a fresh 1_000 heartbeats
                          withHeartbeatBudget 1_000_000_000_000 dflt x -- `x` gets a fresh 200_000_000 heartbeats
                          withHeartbeatBudget 0 dflt x                 -- `x` gets what is left of the ambient allowance
                          -- ambient `maxHeartbeats 0`
                          withHeartbeatBudget 1_000 dflt x             -- `x` gets a fresh 1_000 heartbeats
                          
                          Equations
                          • One or more equations did not get rendered due to their size.
                          Instances For

                            Given a declaration with constant info const and value source code src.body (as well as a linter config and class graph), return an array of verified, graded weakenings that could be applied to the declaration.

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