feat: new variable command

This commit is contained in:
Sebastian Ullrich 2024-07-23 18:35:37 +02:00
parent 88a7f5c592
commit 9f76cb9aa5
8 changed files with 194 additions and 31 deletions

View file

@ -502,6 +502,16 @@ def elabRunMeta : CommandElab := fun stx =>
addDocString declName (← getDocStringText doc)
| _ => throwUnsupportedSyntax
@[builtin_command_elab Lean.Parser.Command.include] def elabInclude : CommandElab
| `(Lean.Parser.Command.include| include $ids*) => do
let vars := (← getScope).varDecls.concatMap getBracketedBinderIds
for id in ids do
unless vars.contains id.getId do
throwError "invalid 'include', variable '{id}' has not been declared in the current scope"
modifyScope fun sc =>
{ sc with includedVars := sc.includedVars ++ ids.toList.map (·.getId) }
| _ => throwUnsupportedSyntax
@[builtin_command_elab Parser.Command.exit] def elabExit : CommandElab := fun _ =>
logWarning "using 'exit' to interrupt Lean"

View file

@ -51,6 +51,8 @@ structure Scope where
even if they do not work with binders per se.
-/
varDecls : Array (TSyntax ``Parser.Term.bracketedBinder) := #[]
/-- `include`d section variable names -/
includedVars : List Name := []
/--
Globally unique internal identifiers for the `varDecls`.
There is one identifier per variable introduced by the binders

View file

@ -327,7 +327,45 @@ def instantiateMVarsProfiling (e : Expr) : MetaM Expr := do
profileitM Exception s!"instantiate metavars" (← getOptions) do
instantiateMVars e
private def elabFunValues (headers : Array DefViewElabHeader) : TermElabM (Array Expr) :=
/--
Runs `k` with a restricted local context where only section variables from `vars` are included that
* are directly referenced in any `headers`,
* are included in `includedVars` (via the `include` command),
* are directly referenced in any variable included by these rules, OR
* are instance-implicit variables that only reference section variables included by these rules.
-/
private def withHeaderSecVars {α} (vars : Array Expr) (includedVars : List Name) (headers : Array DefViewElabHeader)
(k : Array Expr → TermElabM α) : TermElabM α := do
let (_, used) ← collectUsed.run {}
let (lctx, localInsts, vars) ← removeUnused vars used
withLCtx lctx localInsts <| k vars
where
collectUsed : StateRefT CollectFVars.State MetaM Unit := do
-- directly referenced in headers
headers.forM (·.type.collectFVars)
-- included by `include`
vars.forM fun var => do
let ldecl ← getFVarLocalDecl var
if includedVars.contains ldecl.userName then
modify (·.add ldecl.fvarId)
-- transitively referenced
get >>= (·.addDependencies) >>= set
-- instances (`addDependencies` unnecessary as by definition they may only reference variables
-- already included)
vars.forM fun var => do
let ldecl ← getFVarLocalDecl var
let st ← get
if ldecl.binderInfo.isInstImplicit && (← getFVars ldecl.type).all st.fvarSet.contains then
modify (·.add ldecl.fvarId)
getFVars (e : Expr) : MetaM (Array FVarId) :=
(·.2.fvarIds) <$> e.collectFVars.run {}
register_builtin_option deprecated.oldSectionVars : Bool := {
defValue := false
descr := "re-enable deprecated behavior of including exactly the section variables used in a declaration"
}
private def elabFunValues (headers : Array DefViewElabHeader) (vars : Array Expr) (includedVars : List Name) : TermElabM (Array Expr) :=
headers.mapM fun header => do
let mut reusableResult? := none
if let some snap := header.bodySnap? then
@ -342,6 +380,7 @@ private def elabFunValues (headers : Array DefViewElabHeader) : TermElabM (Array
withReuseContext header.value do
withDeclName header.declName <| withLevelNames header.levelNames do
let valStx ← liftMacroM <| declValToTerm header.value
(if header.kind.isTheorem && !deprecated.oldSectionVars.get (← getOptions) then withHeaderSecVars vars includedVars #[header] else fun x => x #[]) fun vars => do
forallBoundedTelescope header.type header.numParams fun xs type => do
-- Add new info nodes for new fvars. The server will detect all fvars of a binder by the binder's source location.
for i in [0:header.binderIds.size] do
@ -353,7 +392,20 @@ private def elabFunValues (headers : Array DefViewElabHeader) : TermElabM (Array
-- NOTE: without this `instantiatedMVars`, `mkLambdaFVars` may leave around a redex that
-- leads to more section variables being included than necessary
let val ← instantiateMVarsProfiling val
mkLambdaFVars xs val
let val ← mkLambdaFVars xs val
unless header.type.hasSorry || val.hasSorry do
for var in vars do
unless header.type.containsFVar var.fvarId! ||
val.containsFVar var.fvarId! ||
(← vars.anyM (fun v => return (← v.fvarId!.getType).containsFVar var.fvarId!)) do
let varDecl ← var.fvarId!.getDecl
let var := if varDecl.userName.hasMacroScopes && varDecl.binderInfo.isInstImplicit then
m!"[{varDecl.type}]".group
else
var
logWarningAt header.ref m!"included section variable '{var}' is not used in \
'{header.declName}', consider excluding it"
return val
if let some snap := header.bodySnap? then
snap.new.resolve <| some {
diagnostics :=
@ -904,7 +956,7 @@ partial def checkForHiddenUnivLevels (allUserLevelNames : List Name) (preDefs :
for preDef in preDefs do
checkPreDef preDef
def elabMutualDef (vars : Array Expr) (views : Array DefView) : TermElabM Unit :=
def elabMutualDef (vars : Array Expr) (includedVars : List Name) (views : Array DefView) : TermElabM Unit :=
if isExample views then
withoutModifyingEnv do
-- save correct environment in info tree
@ -925,7 +977,7 @@ where
addLocalVarInfo view.declId funFVar
let values ←
try
let values ← elabFunValues headers
let values ← elabFunValues headers vars includedVars
Term.synthesizeSyntheticMVarsNoPostponing
values.mapM (instantiateMVarsProfiling ·)
catch ex =>
@ -935,7 +987,7 @@ where
let letRecsToLift ← getLetRecsToLift
let letRecsToLift ← letRecsToLift.mapM instantiateMVarsAtLetRecToLift
checkLetRecsToLiftTypes funFVars letRecsToLift
withUsed vars headers values letRecsToLift fun vars => do
(if headers.all (·.kind.isTheorem) && !deprecated.oldSectionVars.get (← getOptions) then withHeaderSecVars vars includedVars headers else withUsed vars headers values letRecsToLift) fun vars => do
let preDefs ← MutualClosure.main vars headers funFVars values letRecsToLift
for preDef in preDefs do
trace[Elab.definition] "{preDef.declName} : {preDef.type} :=\n{preDef.value}"
@ -1006,7 +1058,8 @@ def elabMutualDef (ds : Array Syntax) : CommandElabM Unit := do
if let some snap := snap? then
-- no non-fatal diagnostics at this point
snap.new.resolve <| .ofTyped { defs, diagnostics := .empty : DefsParsedSnapshot }
runTermElabM fun vars => Term.elabMutualDef vars views
let includedVars := (← getScope).includedVars
runTermElabM fun vars => Term.elabMutualDef vars includedVars views
builtin_initialize
registerTraceClass `Elab.definition.mkClosure

View file

@ -242,10 +242,10 @@ def «structure» := leading_parser
@[builtin_command_parser] def noncomputableSection := leading_parser
"noncomputable " >> "section" >> optional (ppSpace >> checkColGt >> ident)
/--
A `section`/`end` pair delimits the scope of `variable`, `open`, `set_option`, and `local` commands.
Sections can be nested. `section <id>` provides a label to the section that has to appear with the
matching `end`. In either case, the `end` can be omitted, in which case the section is closed at the
end of the file.
A `section`/`end` pair delimits the scope of `variable`, `include, `open`, `set_option`, and `local`
commands. Sections can be nested. `section <id>` provides a label to the section that has to appear
with the matching `end`. In either case, the `end` can be omitted, in which case the section is
closed at the end of the file.
-/
@[builtin_command_parser] def «section» := leading_parser
"section" >> optional (ppSpace >> checkColGt >> ident)
@ -274,12 +274,12 @@ with `end <id>`. The `end` command is optional at the end of a file.
@[builtin_command_parser] def «end» := leading_parser
"end" >> optional (ppSpace >> checkColGt >> ident)
/-- Declares one or more typed variables, or modifies whether already-declared variables are
implicit.
implicit.
Introduces variables that can be used in definitions within the same `namespace` or `section` block.
When a definition mentions a variable, Lean will add it as an argument of the definition. The
`variable` command is also able to add typeclass parameters. This is useful in particular when
writing many definitions that have parameters in common (see below for an example).
When a definition mentions a variable, Lean will add it as an argument of the definition. This is
useful in particular when writing many definitions that have parameters in common (see below for an
example).
Variable declarations have the same flexibility as regular function paramaters. In particular they
can be [explicit, implicit][binder docs], or [instance implicit][tpil classes] (in which case they
@ -287,17 +287,22 @@ can be anonymous). This can be changed, for instance one can turn explicit varia
implicit one with `variable {x}`. Note that currently, you should avoid changing how variables are
bound and declare new variables at the same time; see [issue 2789] for more on this topic.
In *theorem bodies* (i.e. proofs), variables are not included based on usage in order to ensure that
changes to the proof cannot change the statement of the overall theorem. Instead, variables are only
available to the proof if they have been mentioned in the theorem header or in an `include` command
or are instance implicit and depend only on such variables.
See [*Variables and Sections* from Theorem Proving in Lean][tpil vars] for a more detailed
discussion.
[tpil vars]: https://lean-lang.org/theorem_proving_in_lean4/dependent_type_theory.html#variables-and-sections
(Variables and Sections on Theorem Proving in Lean)
[tpil classes]: https://lean-lang.org/theorem_proving_in_lean4/type_classes.html
(Type classes on Theorem Proving in Lean)
[binder docs]: https://leanprover-community.github.io/mathlib4_docs/Lean/Expr.html#Lean.BinderInfo
(Documentation for the BinderInfo type)
[issue 2789]: https://github.com/leanprover/lean4/issues/2789
(Issue 2789 on github)
[tpil vars]:
https://lean-lang.org/theorem_proving_in_lean4/dependent_type_theory.html#variables-and-sections
(Variables and Sections on Theorem Proving in Lean) [tpil classes]:
https://lean-lang.org/theorem_proving_in_lean4/type_classes.html (Type classes on Theorem Proving in
Lean) [binder docs]:
https://leanprover-community.github.io/mathlib4_docs/Lean/Expr.html#Lean.BinderInfo (Documentation
for the BinderInfo type) [issue 2789]: https://github.com/leanprover/lean4/issues/2789 (Issue 2789
on github)
## Examples
@ -368,6 +373,24 @@ namespace Logger
end Logger
```
The following example demonstrates availability of variables in proofs:
```lean
variable
{α : Type} -- available in the proof as indirectly mentioned through `a`
[ToString α] -- available in the proof as `α` is included
(a : α) -- available in the proof as mentioned in the header
{β : Type} -- not available in the proof
[ToString β] -- not available in the proof
theorem ex : a = a := rfl
```
After elaboration of the proof, the following warning will be generated to highlight the unused
hypothesis:
```
included section variable '[ToString α]' is not used in 'ex', consider excluding it
```
In such cases, the offending variable declaration should be moved down or into a section so that
only theorems that do depend on it follow it until the end of the section.
-/
@[builtin_command_parser] def «variable» := leading_parser
"variable" >> many1 (ppSpace >> checkColGt >> Term.bracketedBinder)
@ -703,8 +726,13 @@ list, so it should be brief.
@[builtin_command_parser] def genInjectiveTheorems := leading_parser
"gen_injective_theorems% " >> ident
/-- To be implemented. -/
@[builtin_command_parser] def «include» := leading_parser "include " >> many1 (checkColGt >> ident)
/--
`include eeny meeny` instructs Lean to include the section `variable`s `eeny` and `meeny` in all
declarations in the remainder of the current section, differing from the default behavior of
conditionally including variables based on use in the declaration header. `include` is usually
followed by the `in` combinator to limit the inclusion to the subsequent declaration.
-/
@[builtin_command_parser] def «include» := leading_parser "include " >> many1 ident
/-- No-op parser used as syntax kind for attaching remaining whitespace at the end of the input. -/
@[run_builtin_parser_attribute_hooks] def eoi : Parser := leading_parser ""

View file

@ -8,7 +8,7 @@ options get_default_options() {
// switch to `true` for ABI-breaking changes affecting meta code
opts = opts.update({"interpreter", "prefer_native"}, false);
// switch to `true` for changing built-in parsers used in quotations
opts = opts.update({"internal", "parseQuotWithCurrentStage"}, false);
opts = opts.update({"internal", "parseQuotWithCurrentStage"}, true);
// toggling `parseQuotWithCurrentStage` may also require toggling the following option if macros/syntax
// with custom precheck hooks were affected
opts = opts.update({"quotPrecheck"}, true);

View file

@ -2085,9 +2085,10 @@ variable {R : Type u} {A : Type v} {B : Type w} {C : Type u₁}
section Semiring
variable [Semiring R] [Semiring A] [Semiring B] [Semiring C]
variable [Algebra R A] [Algebra R B] [Algebra R C]
variable [Semiring R] [Semiring A] [Semiring B]
variable [Algebra R A] [Algebra R B]
variable [Semiring C] [Algebra R C] in
instance funLike : FunLike (A →ₐ[R] B) A B where
coe f := f.toFun
@ -2101,6 +2102,7 @@ instance algHomClass : AlgHomClass (A →ₐ[R] B) R A B where
@[ext]
theorem ext {φ₁ φ₂ : A →ₐ[R] B} (H : ∀ x, φ₁ x = φ₂ x) : φ₁ = φ₂ := sorry
variable [Semiring C] [Algebra R C] in
def comp (φ₁ : B →ₐ[R] C) (φ₂ : A →ₐ[R] B) : A →ₐ[R] C :=
{ φ₁.toRingHom.comp φ₂ with
commutes' := sorry }
@ -2400,7 +2402,7 @@ end Mathlib.FieldTheory.Subfield
section Mathlib.FieldTheory.IntermediateField
variable (K L L' : Type _) [Field K] [Field L] [Field L'] [Algebra K L] [Algebra K L']
variable (K L L' : Type _) [Field K] [Field L] [Field L'] [Algebra K L]
structure IntermediateField extends Subalgebra K L where
inv_mem' : ∀ x ∈ carrier, x⁻¹ ∈ carrier
@ -2430,7 +2432,7 @@ end IntermediateField
namespace AlgHom
variable (f : L →ₐ[K] L')
variable [Algebra K L'] (f : L →ₐ[K] L')
def fieldRange : IntermediateField K L' :=
{ f.range, (f : L →+* L').fieldRange with
@ -2446,8 +2448,9 @@ def inclusion {E F : IntermediateField K L} (hEF : E ≤ F) : E →ₐ[K] F :=
section RestrictScalars
variable (K)
variable [Algebra L' L] [IsScalarTower K L' L]
variable [Algebra L' L]
variable [Algebra K L'] [IsScalarTower K L' L] in
def restrictScalars (E : IntermediateField L' L) : IntermediateField K L :=
{ E.toSubfield, E.toSubalgebra.restrictScalars K with
carrier := E.carrier
@ -2470,17 +2473,20 @@ namespace IntermediateField
section AdjoinDef
variable (F : Type _) [Field F] {E : Type _} [Field E] [Algebra F E] (S : Set E)
variable (F : Type _) {E : Type _} [Field E] (S : Set E)
variable [Field F] [Algebra F E] in
def adjoin : IntermediateField F E :=
{ Subfield.closure (Set.range (algebraMap F E) S) with
inv_mem' := sorry }
variable [Field F] [Algebra F E] in
theorem subset_adjoin : S ⊆ adjoin F S := sorry
theorem subset_adjoin_of_subset_left {F : Subfield E} {T : Set E} (HT : T ⊆ F) : T ⊆ adjoin F S :=
sorry
variable [Field F] [Algebra F E] in
theorem adjoin_subset_adjoin_iff {F' : Type _} [Field F'] [Algebra F' E] {S S' : Set E} :
(adjoin F S : Set E) ⊆ adjoin F' S' ↔
Set.range (algebraMap F E) ⊆ adjoin F' S' ∧ S ⊆ adjoin F' S' := sorry

View file

@ -1,5 +1,6 @@
variable (t1 t2 t3 t4 : Nat)
variable (pf12 : t1 = t2) (pf23 : t2 = t3) (pf34 : t3 = t4)
include pf12 pf23 pf34
theorem foo : t1 = t4 :=
calc
@ -9,6 +10,7 @@ theorem foo : t1 = t4 :=
variable (t5 : Nat)
variable (pf23' : t2 < t3) (pf45' : t4 < t5)
include pf23' pf45'
instance [LT α] : Trans (α := α) (· < ·) (· < ·) (· < ·) where
trans := sorry

View file

@ -0,0 +1,62 @@
/-! # Basic section variable tests -/
/-! Directly referenced variables should be included. -/
variable {n : Nat} in
theorem t1 : n = n := by induction n <;> rfl
/-! Variables mentioned only in the body should not be included. -/
variable {n : Nat} in
/-- error: unknown identifier 'n' -/
#guard_msgs in
theorem t2 : ∃ (n : Nat), n = n := by exists n
/-! Variables transitively mentioned should be included. -/
variable {n : Nat} (h : n = n) in
theorem t3 : h = h := rfl
/-! Instance variables mentioning only included variables should be included. -/
variable {α : Type} [ToString α] in
theorem t4 (a : α) : a = a := let _ := toString a; rfl
/-! Instance variables not mentioning only included variables should not be included. -/
variable {α β : Type} [Coe α β] in
/--
error: don't know how to synthesize placeholder
context:
α : Type
a : α
⊢ a = a
-/
#guard_msgs in
theorem t5 (a : α) : a = a := _
/-! Accidentally included variables should be warned for. -/
variable {α : Type} [ToString α] in
/-- warning: included section variable '[ToString α]' is not used in 't6', consider excluding it -/
#guard_msgs in
theorem t6 (a : α) : a = a := rfl
/-! `include` should always include. -/
variable {n : Nat} in
include n in
theorem t7 : ∃ (n : Nat), n = n := by exists n
/-! traversal order bug broke instance inclusion -/
variable {M N : Type} (r : N → N → Prop)
class IsTrans (N : Type) (r : N → N → Prop) : Prop
variable [IsTrans N r] {a b c d : N}
/--
warning: included section variable '[IsTrans N r]' is not used in 'act_rel_of_rel_of_act_rel', consider excluding it
-/
#guard_msgs in
theorem act_rel_of_rel_of_act_rel (ab : r a b) : r a b := ab
/-! More complex include case, instance should be included via `f`. -/
class EquivLike (F : Type) (α β : Type) : Type
variable {F : Type} [EquivLike F α β] (f : F) in
include f in
theorem MulEquiv.decompositionMonoid (_b : β) : α = α :=
let _ : EquivLike F α β := inferInstance; let _ := f; rfl
/-- info: MulEquiv.decompositionMonoid {α β F : Type} [EquivLike F α β] (f : F) (_b : β) : α = α -/
#guard_msgs in
#check MulEquiv.decompositionMonoid