feat: refine auto bound implicit feature

This commit is contained in:
Leonardo de Moura 2022-04-21 10:17:15 -07:00
parent d81c124479
commit 0b92195ec8
7 changed files with 168 additions and 102 deletions

View file

@ -1,6 +1,21 @@
Unreleased
---------
* Refine auto bound implicit feature. It does not consider anymore unbound variables that have the same
name of a declaration being defined. Example:
```lean
def f : f → Bool := -- Error at second `f`
fun _ => true
inductive Foo : List Foo → Type -- Error at second `Foo`
| x : Foo []
```
Before this refinement, the declarations above would be accepted and the
second `f` and `Foo` would be treated as auto implicit variables. That is,
`f : {f : Sort u} → f → Bool`, and
`Foo : {Foo : Type u} → List Foo → Type`.
* Fix syntax hightlighting for recursive declarations. Example
```lean
inductive List (α : Type u) where

View file

@ -67,31 +67,32 @@ structure ElabHeaderResult where
type : Expr
deriving Inhabited
private partial def elabHeaderAux (views : Array InductiveView) (i : Nat) (acc : Array ElabHeaderResult) : TermElabM (Array ElabHeaderResult) := do
if h : i < views.size then
let view := views.get ⟨i, h⟩
let acc ← Term.withAutoBoundImplicit <| Term.elabBinders view.binders.getArgs fun params => do
match view.type? with
| none =>
let u ← mkFreshLevelMVar
let type := mkSort u
Term.synthesizeSyntheticMVarsNoPostponing
Term.addAutoBoundImplicits' params type fun params type => do
pure <| acc.push { lctx := (← getLCtx), localInsts := (← getLocalInstances), params, type, view }
| some typeStx =>
let (type, numImplicitIndices) ← Term.withAutoBoundImplicit do
let type ← Term.elabType typeStx
unless (← isTypeFormerType type) do
throwErrorAt typeStx "invalid inductive type, resultant type is not a sort"
private partial def elabHeaderAux (views : Array InductiveView) (i : Nat) (acc : Array ElabHeaderResult) : TermElabM (Array ElabHeaderResult) :=
Term.withAutoBoundImplicitForbiddenPred (fun n => views.any (·.shortDeclName == n)) do
if h : i < views.size then
let view := views.get ⟨i, h⟩
let acc ← Term.withAutoBoundImplicit <| Term.elabBinders view.binders.getArgs fun params => do
match view.type? with
| none =>
let u ← mkFreshLevelMVar
let type := mkSort u
Term.synthesizeSyntheticMVarsNoPostponing
let indices ← Term.addAutoBoundImplicits #[]
return (← mkForallFVars indices type, indices.size)
Term.addAutoBoundImplicits' params type fun params type => do
trace[Elab.inductive] "header params: {params}, type: {type}"
pure <| acc.push { lctx := (← getLCtx), localInsts := (← getLocalInstances), params, type, view }
elabHeaderAux views (i+1) acc
else
pure acc
Term.addAutoBoundImplicits' params type fun params type => do
pure <| acc.push { lctx := (← getLCtx), localInsts := (← getLocalInstances), params, type, view }
| some typeStx =>
let (type, numImplicitIndices) ← Term.withAutoBoundImplicit do
let type ← Term.elabType typeStx
unless (← isTypeFormerType type) do
throwErrorAt typeStx "invalid inductive type, resultant type is not a sort"
Term.synthesizeSyntheticMVarsNoPostponing
let indices ← Term.addAutoBoundImplicits #[]
return (← mkForallFVars indices type, indices.size)
Term.addAutoBoundImplicits' params type fun params type => do
trace[Elab.inductive] "header params: {params}, type: {type}"
pure <| acc.push { lctx := (← getLCtx), localInsts := (← getLocalInstances), params, type, view }
elabHeaderAux views (i+1) acc
else
pure acc
private def checkNumParams (rs : Array ElabHeaderResult) : TermElabM Nat := do
let numParams := rs[0].params.size

View file

@ -99,51 +99,54 @@ private def getPendindMVarErrorMessage (views : Array DefView) : String :=
"\nwhen the resulting type of a declaration is explicitly provided, all holes (e.g., `_`) in the header are resolved before the declaration body is processed"
private def elabHeaders (views : Array DefView) : TermElabM (Array DefViewElabHeader) := do
let mut headers := #[]
for view in views do
let newHeader ← withRef view.ref do
let ⟨shortDeclName, declName, levelNames⟩ ← Term.expandDeclId (← getCurrNamespace) (← getLevelNames) view.declId view.modifiers
addDeclarationRanges declName view.ref
applyAttributesAt declName view.modifiers.attrs AttributeApplicationTime.beforeElaboration
withDeclName declName <| withAutoBoundImplicit <| withLevelNames levelNames <|
elabBindersEx view.binders.getArgs fun xs => do
let refForElabFunType := view.value
let type ← match view.type? with
| some typeStx =>
let type ← elabType typeStx
registerFailedToInferDefTypeInfo type typeStx
pure type
| none =>
let hole := mkHole refForElabFunType
let type ← elabType hole
trace[Elab.definition] ">> type: {type}\n{type.mvarId!}"
registerFailedToInferDefTypeInfo type refForElabFunType
pure type
Term.synthesizeSyntheticMVarsNoPostponing
let (binderIds, xs) := xs.unzip
let xs ← addAutoBoundImplicits xs
let type ← mkForallFVars' xs type
let type ← instantiateMVars type
let levelNames ← getLevelNames
if view.type?.isSome then
let pendingMVarIds ← getMVars type
discard <| logUnassignedUsingErrorInfos pendingMVarIds <|
getPendindMVarErrorMessage views
let newHeader := {
ref := view.ref,
modifiers := view.modifiers,
kind := view.kind,
shortDeclName := shortDeclName,
declName := declName,
levelNames := levelNames,
binderIds := binderIds,
numParams := xs.size,
type := type,
valueStx := view.value : DefViewElabHeader }
check headers newHeader
return newHeader
headers := headers.push newHeader
pure headers
let expandedDeclIds ← views.mapM fun view => withRef view.ref do
Term.expandDeclId (← getCurrNamespace) (← getLevelNames) view.declId view.modifiers
withAutoBoundImplicitForbiddenPred (fun n => expandedDeclIds.any (·.shortName == n)) do
let mut headers := #[]
for view in views, ⟨shortDeclName, declName, levelNames⟩ in expandedDeclIds do
let newHeader ← withRef view.ref do
addDeclarationRanges declName view.ref
applyAttributesAt declName view.modifiers.attrs AttributeApplicationTime.beforeElaboration
withDeclName declName <| withAutoBoundImplicit <| withLevelNames levelNames <|
elabBindersEx view.binders.getArgs fun xs => do
let refForElabFunType := view.value
let type ← match view.type? with
| some typeStx =>
let type ← elabType typeStx
registerFailedToInferDefTypeInfo type typeStx
pure type
| none =>
let hole := mkHole refForElabFunType
let type ← elabType hole
trace[Elab.definition] ">> type: {type}\n{type.mvarId!}"
registerFailedToInferDefTypeInfo type refForElabFunType
pure type
Term.synthesizeSyntheticMVarsNoPostponing
let (binderIds, xs) := xs.unzip
-- TODO: add forbidden predicate using `shortDeclName` from `views`
let xs ← addAutoBoundImplicits xs
let type ← mkForallFVars' xs type
let type ← instantiateMVars type
let levelNames ← getLevelNames
if view.type?.isSome then
let pendingMVarIds ← getMVars type
discard <| logUnassignedUsingErrorInfos pendingMVarIds <|
getPendindMVarErrorMessage views
let newHeader := {
ref := view.ref,
modifiers := view.modifiers,
kind := view.kind,
shortDeclName := shortDeclName,
declName := declName,
levelNames := levelNames,
binderIds := binderIds,
numParams := xs.size,
type := type,
valueStx := view.value : DefViewElabHeader }
check headers newHeader
return newHeader
headers := headers.push newHeader
return headers
private partial def withFunLocalDecls {α} (headers : Array DefViewElabHeader) (k : Array Expr → TermElabM α) : TermElabM α :=
let rec loop (i : Nat) (fvars : Array Expr) := do

View file

@ -485,14 +485,15 @@ where
else
k infos copiedParents
private def elabFieldTypeValue (view : StructFieldView) : TermElabM (Option Expr × Option Expr) := do
Term.withAutoBoundImplicit <| Term.elabBinders view.binders.getArgs fun params => do
private def elabFieldTypeValue (view : StructFieldView) : TermElabM (Option Expr × Option Expr) :=
Term.withAutoBoundImplicit <| Term.withAutoBoundImplicitForbiddenPred (fun n => view.name == n) <| Term.elabBinders view.binders.getArgs fun params => do
match view.type? with
| none =>
match view.value? with
| none => return (none, none)
| some valStx =>
Term.synthesizeSyntheticMVarsNoPostponing
-- TODO: add forbidden predicate using `shortDeclName` from `view`
let params ← Term.addAutoBoundImplicits params
let value ← Term.elabTerm valStx none
let value ← mkLambdaFVars params value
@ -867,33 +868,34 @@ def elabStructure (modifiers : Modifiers) (stx : Syntax) : CommandElabM Unit :=
runTermElabM none fun scopeVars => do
let scopeLevelNames ← Term.getLevelNames
let ⟨name, declName, allUserLevelNames⟩ ← Elab.expandDeclId (← getCurrNamespace) scopeLevelNames declId modifiers
addDeclarationRanges declName stx
Term.withDeclName declName do
let ctor ← expandCtor stx modifiers declName
let fields ← expandFields stx modifiers declName
Term.withLevelNames allUserLevelNames <| Term.withAutoBoundImplicit <|
Term.elabBinders params fun params => do
Term.synthesizeSyntheticMVarsNoPostponing
let params ← Term.addAutoBoundImplicits params
let allUserLevelNames ← Term.getLevelNames
elabStructureView {
ref := stx
modifiers
scopeLevelNames
allUserLevelNames
declName
isClass
scopeVars
params
parents
type
ctor
fields
}
unless isClass do
mkSizeOfInstances declName
mkInjectiveTheorems declName
return declName
Term.withAutoBoundImplicitForbiddenPred (fun n => name == n) do
addDeclarationRanges declName stx
Term.withDeclName declName do
let ctor ← expandCtor stx modifiers declName
let fields ← expandFields stx modifiers declName
Term.withLevelNames allUserLevelNames <| Term.withAutoBoundImplicit <|
Term.elabBinders params fun params => do
Term.synthesizeSyntheticMVarsNoPostponing
let params ← Term.addAutoBoundImplicits params
let allUserLevelNames ← Term.getLevelNames
elabStructureView {
ref := stx
modifiers
scopeLevelNames
allUserLevelNames
declName
isClass
scopeVars
params
parents
type
ctor
fields
}
unless isClass do
mkSizeOfInstances declName
mkInjectiveTheorems declName
return declName
derivingClassViews.forM fun view => view.applyHandlers #[declName]
builtin_initialize registerTraceClass `Elab.structure

View file

@ -138,11 +138,13 @@ structure Context where
fileMap : FileMap
declName? : Option Name := none
macroStack : MacroStack := []
/- When `mayPostpone == true`, an elaboration function may interrupt its execution by throwing `Exception.postpone`.
/--
When `mayPostpone == true`, an elaboration function may interrupt its execution by throwing `Exception.postpone`.
The function `elabTerm` catches this exception and creates fresh synthetic metavariable `?m`, stores `?m` in
the list of pending synthetic metavariables, and returns `?m`. -/
mayPostpone : Bool := true
/- When `errToSorry` is set to true, the method `elabTerm` catches
/--
When `errToSorry` is set to true, the method `elabTerm` catches
exceptions and converts them into synthetic `sorry`s.
The implementation of choice nodes and overloaded symbols rely on the fact
that when `errToSorry` is set to false for an elaboration function `F`, then
@ -150,13 +152,23 @@ structure Context where
That is, it is safe to transition `errToSorry` from `true` to `false`, but
we must not set `errToSorry` to `true` when it is currently set to `false`. -/
errToSorry : Bool := true
/- When `autoBoundImplicit` is set to true, instead of producing
/--
When `autoBoundImplicit` is set to true, instead of producing
an "unknown identifier" error for unbound variables, we generate an
internal exception. This exception is caught at `elabBinders` and
`elabTypeWithUnboldImplicit`. Both methods add implicit declarations
for the unbound variable and try again. -/
autoBoundImplicit : Bool := false
autoBoundImplicits : Std.PArray Expr := {}
/--
A name `n` is only eligible to be an auto implicit name if `autoBoundImplicitForbidden n = false`.
We use this predicate to disallow `f` to be considered an auto implicit name in a definition such
as
```
def f : f → Bool := fun _ => true
```
-/
autoBoundImplicitForbidden : Name → Bool := fun _ => false
/-- Map from user name to internal unique name -/
sectionVars : NameMap Name := {}
/-- Map from internal name to fvar -/
@ -1374,6 +1386,9 @@ partial def withAutoBoundImplicit (k : TermElabM α) : TermElabM α := do
def withoutAutoBoundImplicit (k : TermElabM α) : TermElabM α := do
withReader (fun ctx => { ctx with autoBoundImplicit := false, autoBoundImplicits := {} }) k
partial def withAutoBoundImplicitForbiddenPred (p : Name → Bool) (x : TermElabM α) : TermElabM α := do
withReader (fun ctx => { ctx with autoBoundImplicitForbidden := fun n => p n || ctx.autoBoundImplicitForbidden n }) x
/--
Collect unassigned metavariables in `type` that are not already in `init` and not satisfying `except`.
-/
@ -1525,7 +1540,9 @@ def resolveName (stx : Syntax) (n : Name) (preresolved : List (Name × List Stri
process preresolved
where process (candidates : List (Name × List String)) : TermElabM (List (Expr × List String)) := do
if candidates.isEmpty then
if (← read).autoBoundImplicit && isValidAutoBoundImplicitName n (relaxedAutoImplicit.get (← getOptions)) then
if (← read).autoBoundImplicit &&
!(← read).autoBoundImplicitForbidden n &&
isValidAutoBoundImplicitName n (relaxedAutoImplicit.get (← getOptions)) then
throwAutoBoundImplicitLocal n
else
throwError "unknown identifier '{Lean.mkConst n}'"

View file

@ -0,0 +1,23 @@
def f : f → Bool := -- Error at second `f`
fun _ => true
mutual
def g : h → Bool := -- Error at `h`, `h` is not eligible to be an auto implicit because of the mutual block
fun _ => true
def h := List Nat
end
structure Bla (x : List Bla) where -- Error at second `Foo`
val : Nat
inductive Foo : List Foo -> Type -- Error at second `Foo`
| x : Foo []
mutual
inductive Ex1 : Ex2 → Type -- Error at `Ex2`
inductive Ex2 : Type
end

View file

@ -0,0 +1,5 @@
autoImplicitForbidden.lean:1:8-1:9: error: unknown identifier 'f'
autoImplicitForbidden.lean:6:10-6:11: error: unknown identifier 'h'
autoImplicitForbidden.lean:13:24-13:27: error: unknown identifier 'Bla'
autoImplicitForbidden.lean:16:21-16:24: error: unknown identifier 'Foo'
autoImplicitForbidden.lean:20:18-20:21: error: unknown identifier 'Ex2'