fix: move auxDeclToFullName to LocalContext to fix name (un)resolution (#7075)
This PR ensures that names suggested by tactics like `simp?` are not shadowed by auxiliary declarations in the local context and that names of `let rec` and `where` declarations are correctly resolved in tactic blocks. This PR contains the following potentially breaking changes: * Moves the `auxDeclToFullName` map from `TermElab.Context` to `LocalContext`. * Refactors `Lean.Elab.Term.resolveLocalName : Name → TermElabM …` to `Lean.resolveLocalName [MonadResolveName m] [MonadEnv m] [MonadLCtx m] : Name → m …`. * Refactors the `TermElabM` action `Lean.Elab.Term.withAuxDecl` to a monad-polymorphic action `Lean.Meta.withAuxDecl`. * Adds an optional `filter` argument to `Lean.unresolveNameGlobal`. Closes #6706, closes #7073.
This commit is contained in:
parent
d3eb2fe13c
commit
e337129108
10 changed files with 293 additions and 212 deletions
|
|
@ -1215,7 +1215,7 @@ private def resolveLValAux (e : Expr) (eType : Expr) (lval : LVal) : TermElabM L
|
|||
let fullName := Name.mkStr structName fieldName
|
||||
for localDecl in (← getLCtx) do
|
||||
if localDecl.isAuxDecl then
|
||||
if let some localDeclFullName := (← read).auxDeclToFullName.find? localDecl.fvarId then
|
||||
if let some localDeclFullName := (← getLCtx).auxDeclToFullName.find? localDecl.fvarId then
|
||||
if fullName == (privateToUserName? localDeclFullName).getD localDeclFullName then
|
||||
/- LVal notation is being used to make a "local" recursive call. -/
|
||||
return LValResolution.localRec structName fullName localDecl.toExpr
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ private def withInductiveLocalDecls (rs : Array PreElabHeaderResult) (x : Array
|
|||
let rec loop (i : Nat) (indFVars : Array Expr) := do
|
||||
if h : i < namesAndTypes.size then
|
||||
let (declName, shortDeclName, type) := namesAndTypes[i]
|
||||
Term.withAuxDecl shortDeclName type declName fun indFVar => loop (i+1) (indFVars.push indFVar)
|
||||
withAuxDecl shortDeclName type declName fun indFVar => loop (i+1) (indFVars.push indFVar)
|
||||
else
|
||||
x params indFVars
|
||||
loop 0 #[]
|
||||
|
|
|
|||
|
|
@ -258,10 +258,6 @@ namespace Term
|
|||
|
||||
structure Context where
|
||||
declName? : Option Name := none
|
||||
/--
|
||||
Map `.auxDecl` local declarations used to encode recursive declarations to their full-names.
|
||||
-/
|
||||
auxDeclToFullName : FVarIdMap Name := {}
|
||||
macroStack : MacroStack := []
|
||||
/--
|
||||
When `mayPostpone == true`, an elaboration function may interrupt its execution by throwing `Exception.postpone`.
|
||||
|
|
@ -627,15 +623,6 @@ def withLevelNames (levelNames : List Name) (x : TermElabM α) : TermElabM α :=
|
|||
setLevelNames levelNames
|
||||
try x finally setLevelNames levelNamesSaved
|
||||
|
||||
/--
|
||||
Declare an auxiliary local declaration `shortDeclName : type` for elaborating recursive declaration `declName`,
|
||||
update the mapping `auxDeclToFullName`, and then execute `k`.
|
||||
-/
|
||||
def withAuxDecl (shortDeclName : Name) (type : Expr) (declName : Name) (k : Expr → TermElabM α) : TermElabM α :=
|
||||
withLocalDecl shortDeclName .default (kind := .auxDecl) type fun x =>
|
||||
withReader (fun ctx => { ctx with auxDeclToFullName := ctx.auxDeclToFullName.insert x.fvarId! declName }) do
|
||||
k x
|
||||
|
||||
def withoutErrToSorryImp (x : TermElabM α) : TermElabM α :=
|
||||
withReader (fun ctx => { ctx with errToSorry := false }) x
|
||||
|
||||
|
|
@ -1522,169 +1509,6 @@ def blockImplicitLambda (stx : Syntax) : Bool :=
|
|||
isExplicit stx || isExplicitApp stx || isLambdaWithImplicit stx || isHole stx || isTacticBlock stx ||
|
||||
isNoImplicitLambda stx || isTypeAscription stx
|
||||
|
||||
def resolveLocalName (n : Name) : TermElabM (Option (Expr × List String)) := do
|
||||
let lctx ← getLCtx
|
||||
let auxDeclToFullName := (← read).auxDeclToFullName
|
||||
let currNamespace ← getCurrNamespace
|
||||
let view := extractMacroScopes n
|
||||
/- Simple case. "Match" function for regular local declarations. -/
|
||||
let matchLocalDecl? (localDecl : LocalDecl) (givenName : Name) : Option LocalDecl := do
|
||||
guard (localDecl.userName == givenName)
|
||||
return localDecl
|
||||
/-
|
||||
"Match" function for auxiliary declarations that correspond to recursive definitions being defined.
|
||||
This function is used in the first-pass.
|
||||
Note that we do not check for `localDecl.userName == givenName` in this pass as we do for regular local declarations.
|
||||
Reason: consider the following example
|
||||
```
|
||||
mutual
|
||||
inductive Foo
|
||||
| somefoo : Foo | bar : Bar → Foo → Foo
|
||||
inductive Bar
|
||||
| somebar : Bar| foobar : Foo → Bar → Bar
|
||||
end
|
||||
|
||||
mutual
|
||||
private def Foo.toString : Foo → String
|
||||
| Foo.somefoo => go 2 ++ toString.go 2 ++ Foo.toString.go 2
|
||||
| Foo.bar b f => toString f ++ Bar.toString b
|
||||
where
|
||||
go (x : Nat) := s!"foo {x}"
|
||||
|
||||
private def _root_.Ex2.Bar.toString : Bar → String
|
||||
| Bar.somebar => "bar"
|
||||
| Bar.foobar f b => Foo.toString f ++ Bar.toString b
|
||||
end
|
||||
```
|
||||
In the example above, we have two local declarations named `toString` in the local context, and
|
||||
we want the `toString f` to be resolved to `Foo.toString f`.
|
||||
-/
|
||||
let matchAuxRecDecl? (localDecl : LocalDecl) (fullDeclName : Name) (givenNameView : MacroScopesView) : Option LocalDecl := do
|
||||
let fullDeclView := extractMacroScopes fullDeclName
|
||||
/- First cleanup private name annotations -/
|
||||
let fullDeclView := { fullDeclView with name := (privateToUserName? fullDeclView.name).getD fullDeclView.name }
|
||||
let fullDeclName := fullDeclView.review
|
||||
let localDeclNameView := extractMacroScopes localDecl.userName
|
||||
/- If the current namespace is a prefix of the full declaration name,
|
||||
we use a relaxed matching test where we must satisfy the following conditions
|
||||
- The local declaration is a suffix of the given name.
|
||||
- The given name is a suffix of the full declaration.
|
||||
|
||||
Recall the `let rec`/`where` declaration naming convention. For example, suppose we have
|
||||
```
|
||||
def Foo.Bla.f ... :=
|
||||
... go ...
|
||||
where
|
||||
go ... := ...
|
||||
```
|
||||
The current namespace is `Foo.Bla`, and the full name for `go` is `Foo.Bla.f.g`, but we want to
|
||||
refer to it using just `go`. It is also accepted to refer to it using `f.go`, `Bla.f.go`, etc.
|
||||
|
||||
-/
|
||||
if currNamespace.isPrefixOf fullDeclName then
|
||||
/- Relaxed mode that allows us to access `let rec` declarations using shorter names -/
|
||||
guard (localDeclNameView.isSuffixOf givenNameView)
|
||||
guard (givenNameView.isSuffixOf fullDeclView)
|
||||
return localDecl
|
||||
else
|
||||
/-
|
||||
It is the standard algorithm we are using at `resolveGlobalName` for processing namespaces.
|
||||
|
||||
The current solution also has a limitation when using `def _root_` in a mutual block.
|
||||
The non `def _root_` declarations may update the namespace. See the following example:
|
||||
```
|
||||
mutual
|
||||
def Foo.f ... := ...
|
||||
def _root_.g ... := ...
|
||||
let rec h := ...
|
||||
...
|
||||
end
|
||||
```
|
||||
`def Foo.f` updates the namespace. Then, even when processing `def _root_.g ...`
|
||||
the condition `currNamespace.isPrefixOf fullDeclName` does not hold.
|
||||
This is not a big problem because we are planning to modify how we handle the mutual block in the future.
|
||||
|
||||
Note that we don't check for `localDecl.userName == givenName` here.
|
||||
-/
|
||||
let rec go (ns : Name) : Option LocalDecl := do
|
||||
if { givenNameView with name := ns ++ givenNameView.name }.review == fullDeclName then
|
||||
return localDecl
|
||||
match ns with
|
||||
| .str pre .. => go pre
|
||||
| _ => failure
|
||||
return (← go currNamespace)
|
||||
/- Traverse the local context backwards looking for match `givenNameView`.
|
||||
If `skipAuxDecl` we ignore `auxDecl` local declarations. -/
|
||||
let findLocalDecl? (givenNameView : MacroScopesView) (skipAuxDecl : Bool) : Option LocalDecl :=
|
||||
let givenName := givenNameView.review
|
||||
let localDecl? := lctx.decls.findSomeRev? fun localDecl? => do
|
||||
let localDecl ← localDecl?
|
||||
if localDecl.isAuxDecl then
|
||||
guard (!skipAuxDecl)
|
||||
if let some fullDeclName := auxDeclToFullName.find? localDecl.fvarId then
|
||||
matchAuxRecDecl? localDecl fullDeclName givenNameView
|
||||
else
|
||||
matchLocalDecl? localDecl givenName
|
||||
else
|
||||
matchLocalDecl? localDecl givenName
|
||||
if localDecl?.isSome || skipAuxDecl then
|
||||
localDecl?
|
||||
else
|
||||
-- Search auxDecls again trying an exact match of the given name
|
||||
lctx.decls.findSomeRev? fun localDecl? => do
|
||||
let localDecl ← localDecl?
|
||||
guard localDecl.isAuxDecl
|
||||
matchLocalDecl? localDecl givenName
|
||||
/-
|
||||
We use the parameter `globalDeclFound` to decide whether we should skip auxiliary declarations or not.
|
||||
We set it to true if we found a global declaration `n` as we iterate over the `loop`.
|
||||
Without this workaround, we would not be able to elaborate an example such as
|
||||
```
|
||||
def foo.aux := 1
|
||||
def foo : Nat → Nat
|
||||
| n => foo.aux -- should not be interpreted as `(foo).aux`
|
||||
```
|
||||
See test `aStructPerfIssue.lean` for another example.
|
||||
We skip auxiliary declarations when `projs` is not empty and `globalDeclFound` is true.
|
||||
Remark: we did not use to have the `globalDeclFound` parameter. Without this extra check we failed
|
||||
to elaborate
|
||||
```
|
||||
example : Nat :=
|
||||
let n := 0
|
||||
n.succ + (m |>.succ) + m.succ
|
||||
where
|
||||
m := 1
|
||||
```
|
||||
See issue #1850.
|
||||
-/
|
||||
let rec loop (n : Name) (projs : List String) (globalDeclFound : Bool) := do
|
||||
let givenNameView := { view with name := n }
|
||||
let mut globalDeclFoundNext := globalDeclFound
|
||||
unless globalDeclFound do
|
||||
let r ← resolveGlobalName givenNameView.review
|
||||
let r := r.filter fun (_, fieldList) => fieldList.isEmpty
|
||||
unless r.isEmpty do
|
||||
globalDeclFoundNext := true
|
||||
/-
|
||||
Note that we use `globalDeclFound` instead of `globalDeclFoundNext` in the following test.
|
||||
Reason: a local should shadow a global with the same name.
|
||||
Consider the following example. See issue #3079
|
||||
```
|
||||
def foo : Nat := 1
|
||||
|
||||
def bar : Nat :=
|
||||
foo.add 1 -- should be 11
|
||||
where
|
||||
foo := 10
|
||||
```
|
||||
-/
|
||||
match findLocalDecl? givenNameView (skipAuxDecl := globalDeclFound && !projs.isEmpty) with
|
||||
| some decl => return some (decl.toExpr, projs)
|
||||
| none => match n with
|
||||
| .str pre s => loop pre (s::projs) globalDeclFoundNext
|
||||
| _ => return none
|
||||
loop view.name [] (globalDeclFound := false)
|
||||
|
||||
/-- Return true iff `stx` is a `Syntax.ident`, and it is a local variable. -/
|
||||
def isLocalIdent? (stx : Syntax) : TermElabM (Option Expr) :=
|
||||
match stx with
|
||||
|
|
|
|||
|
|
@ -159,14 +159,16 @@ end LocalDecl
|
|||
|
||||
/-- A LocalContext is an ordered set of local variable declarations.
|
||||
It is used to store the free variables (also known as local constants) that
|
||||
are in scope.
|
||||
are in scope. It also maps free variables corresponding to auxiliary declarations
|
||||
(recursive references and `where` and `let rec` bindings) to their fully-qualified global names.
|
||||
|
||||
When inspecting a goal or expected type in the infoview, the local
|
||||
context is all of the variables above the `⊢` symbol.
|
||||
-/
|
||||
structure LocalContext where
|
||||
fvarIdToDecl : PersistentHashMap FVarId LocalDecl := {}
|
||||
decls : PersistentArray (Option LocalDecl) := {}
|
||||
fvarIdToDecl : PersistentHashMap FVarId LocalDecl := {}
|
||||
decls : PersistentArray (Option LocalDecl) := {}
|
||||
auxDeclToFullName : FVarIdMap Name := {}
|
||||
deriving Inhabited
|
||||
|
||||
namespace LocalContext
|
||||
|
|
@ -186,10 +188,10 @@ It should not be used directly since the argument `(fvarId : FVarId)` is
|
|||
assumed to be unique. You can create a unique fvarId with `mkFreshFVarId`. -/
|
||||
def mkLocalDecl (lctx : LocalContext) (fvarId : FVarId) (userName : Name) (type : Expr) (bi : BinderInfo := BinderInfo.default) (kind : LocalDeclKind := .default) : LocalContext :=
|
||||
match lctx with
|
||||
| { fvarIdToDecl := map, decls := decls } =>
|
||||
| { fvarIdToDecl := map, decls := decls, auxDeclToFullName } =>
|
||||
let idx := decls.size
|
||||
let decl := LocalDecl.cdecl idx fvarId userName type bi kind
|
||||
{ fvarIdToDecl := map.insert fvarId decl, decls := decls.push decl }
|
||||
{ fvarIdToDecl := map.insert fvarId decl, decls := decls.push decl, auxDeclToFullName }
|
||||
|
||||
-- `mkLocalDecl` without `kind`
|
||||
@[export lean_local_ctx_mk_local_decl]
|
||||
|
|
@ -199,23 +201,34 @@ private def mkLocalDeclExported (lctx : LocalContext) (fvarId : FVarId) (userNam
|
|||
/-- Low level API for let declarations. Do not use directly.-/
|
||||
def mkLetDecl (lctx : LocalContext) (fvarId : FVarId) (userName : Name) (type : Expr) (value : Expr) (nonDep := false) (kind : LocalDeclKind := default) : LocalContext :=
|
||||
match lctx with
|
||||
| { fvarIdToDecl := map, decls := decls } =>
|
||||
| { fvarIdToDecl := map, decls := decls, auxDeclToFullName } =>
|
||||
let idx := decls.size
|
||||
let decl := LocalDecl.ldecl idx fvarId userName type value nonDep kind
|
||||
{ fvarIdToDecl := map.insert fvarId decl, decls := decls.push decl }
|
||||
{ fvarIdToDecl := map.insert fvarId decl, decls := decls.push decl, auxDeclToFullName }
|
||||
|
||||
@[export lean_local_ctx_mk_let_decl]
|
||||
private def mkLetDeclExported (lctx : LocalContext) (fvarId : FVarId) (userName : Name) (type : Expr) (value : Expr) (nonDep : Bool) : LocalContext :=
|
||||
mkLetDecl lctx fvarId userName type value nonDep
|
||||
|
||||
/-- Low level API for auxiliary declarations. Do not use directly. -/
|
||||
def mkAuxDecl (lctx : LocalContext) (fvarId : FVarId) (userName : Name) (type : Expr) (fullName : Name) : LocalContext :=
|
||||
match lctx with
|
||||
| { fvarIdToDecl := map, decls := decls, auxDeclToFullName } =>
|
||||
let idx := decls.size
|
||||
let decl := LocalDecl.cdecl idx fvarId userName type .default .auxDecl
|
||||
let auxDeclToFullName := auxDeclToFullName.insert fvarId fullName
|
||||
{ fvarIdToDecl := map.insert fvarId decl, decls := decls.push decl, auxDeclToFullName }
|
||||
|
||||
/-- Low level API for adding a local declaration.
|
||||
Do not use directly. -/
|
||||
def addDecl (lctx : LocalContext) (newDecl : LocalDecl) : LocalContext :=
|
||||
match lctx with
|
||||
| { fvarIdToDecl := map, decls := decls } =>
|
||||
| { fvarIdToDecl := map, decls := decls, auxDeclToFullName } =>
|
||||
let idx := decls.size
|
||||
let newDecl := newDecl.setIndex idx
|
||||
{ fvarIdToDecl := map.insert newDecl.fvarId newDecl, decls := decls.push newDecl }
|
||||
{ fvarIdToDecl := map.insert newDecl.fvarId newDecl
|
||||
decls := decls.push newDecl
|
||||
auxDeclToFullName }
|
||||
|
||||
@[export lean_local_ctx_find]
|
||||
def find? (lctx : LocalContext) (fvarId : FVarId) : Option LocalDecl :=
|
||||
|
|
@ -260,18 +273,26 @@ private partial def popTailNoneAux (a : PArray (Option LocalDecl)) : PArray (Opt
|
|||
@[export lean_local_ctx_erase]
|
||||
def erase (lctx : LocalContext) (fvarId : FVarId) : LocalContext :=
|
||||
match lctx with
|
||||
| { fvarIdToDecl := map, decls := decls } =>
|
||||
| { fvarIdToDecl := map, decls := decls, auxDeclToFullName } =>
|
||||
match map.find? fvarId with
|
||||
| none => lctx
|
||||
| some decl => { fvarIdToDecl := map.erase fvarId, decls := popTailNoneAux (decls.set decl.index none) }
|
||||
| some decl =>
|
||||
{ fvarIdToDecl := map.erase fvarId
|
||||
decls := popTailNoneAux (decls.set decl.index none)
|
||||
auxDeclToFullName :=
|
||||
if decl.isAuxDecl then auxDeclToFullName.erase fvarId else auxDeclToFullName }
|
||||
|
||||
def pop (lctx : LocalContext): LocalContext :=
|
||||
def pop (lctx : LocalContext) : LocalContext :=
|
||||
match lctx with
|
||||
| { fvarIdToDecl := map, decls := decls } =>
|
||||
| { fvarIdToDecl := map, decls := decls, auxDeclToFullName } =>
|
||||
if _ : decls.size = 0 then lctx
|
||||
else match decls[decls.size - 1] with
|
||||
| none => lctx -- unreachable
|
||||
| some decl => { fvarIdToDecl := map.erase decl.fvarId, decls := popTailNoneAux decls.pop }
|
||||
| some decl =>
|
||||
{ fvarIdToDecl := map.erase decl.fvarId
|
||||
decls := popTailNoneAux decls.pop
|
||||
auxDeclToFullName :=
|
||||
if decl.isAuxDecl then auxDeclToFullName.erase decl.fvarId else auxDeclToFullName }
|
||||
|
||||
def findFromUserName? (lctx : LocalContext) (userName : Name) : Option LocalDecl :=
|
||||
lctx.decls.findSomeRev? fun decl =>
|
||||
|
|
@ -298,18 +319,20 @@ def lastDecl (lctx : LocalContext) : Option LocalDecl :=
|
|||
def setUserName (lctx : LocalContext) (fvarId : FVarId) (userName : Name) : LocalContext :=
|
||||
let decl := lctx.get! fvarId
|
||||
let decl := decl.setUserName userName
|
||||
{ fvarIdToDecl := lctx.fvarIdToDecl.insert decl.fvarId decl,
|
||||
decls := lctx.decls.set decl.index decl }
|
||||
{ fvarIdToDecl := lctx.fvarIdToDecl.insert decl.fvarId decl,
|
||||
decls := lctx.decls.set decl.index decl,
|
||||
auxDeclToFullName := lctx.auxDeclToFullName }
|
||||
|
||||
def renameUserName (lctx : LocalContext) (fromName : Name) (toName : Name) : LocalContext :=
|
||||
match lctx with
|
||||
| { fvarIdToDecl := map, decls := decls } =>
|
||||
| { fvarIdToDecl := map, decls := decls, auxDeclToFullName } =>
|
||||
match lctx.findFromUserName? fromName with
|
||||
| none => lctx
|
||||
| some decl =>
|
||||
let decl := decl.setUserName toName;
|
||||
{ fvarIdToDecl := map.insert decl.fvarId decl,
|
||||
decls := decls.set decl.index decl }
|
||||
decls := decls.set decl.index decl,
|
||||
auxDeclToFullName }
|
||||
|
||||
/--
|
||||
Low-level function for updating the local context.
|
||||
|
|
@ -317,13 +340,14 @@ def renameUserName (lctx : LocalContext) (fromName : Name) (toName : Name) : Loc
|
|||
the `index` nor `fvarId` are modified. -/
|
||||
@[inline] def modifyLocalDecl (lctx : LocalContext) (fvarId : FVarId) (f : LocalDecl → LocalDecl) : LocalContext :=
|
||||
match lctx with
|
||||
| { fvarIdToDecl := map, decls := decls } =>
|
||||
| { fvarIdToDecl := map, decls := decls, auxDeclToFullName } =>
|
||||
match lctx.find? fvarId with
|
||||
| none => lctx
|
||||
| some decl =>
|
||||
let decl := f decl
|
||||
{ fvarIdToDecl := map.insert decl.fvarId decl
|
||||
decls := decls.set decl.index decl }
|
||||
decls := decls.set decl.index decl
|
||||
auxDeclToFullName }
|
||||
|
||||
/--
|
||||
Set the kind of the given fvar.
|
||||
|
|
@ -520,6 +544,7 @@ def LocalDecl.replaceFVarId (fvarId : FVarId) (e : Expr) (d : LocalDecl) : Local
|
|||
def LocalContext.replaceFVarId (fvarId : FVarId) (e : Expr) (lctx : LocalContext) : LocalContext :=
|
||||
let lctx := lctx.erase fvarId
|
||||
{ fvarIdToDecl := lctx.fvarIdToDecl.map (·.replaceFVarId fvarId e)
|
||||
decls := lctx.decls.map fun localDecl? => localDecl?.map (·.replaceFVarId fvarId e) }
|
||||
decls := lctx.decls.map fun localDecl? => localDecl?.map (·.replaceFVarId fvarId e),
|
||||
auxDeclToFullName := lctx.auxDeclToFullName }
|
||||
|
||||
end Lean
|
||||
|
|
|
|||
|
|
@ -1664,6 +1664,21 @@ def withLocalDeclsDND [Inhabited α] (declInfos : Array (Name × Expr)) (k : (xs
|
|||
withLocalDeclsD
|
||||
(declInfos.map (fun (name, typeCtor) => (name, fun _ => pure typeCtor))) k
|
||||
|
||||
private def withAuxDeclImp (shortDeclName : Name) (type : Expr) (declName : Name) (k : Expr → MetaM α) : MetaM α := do
|
||||
let fvarId ← mkFreshFVarId
|
||||
let ctx ← read
|
||||
let lctx := ctx.lctx.mkAuxDecl fvarId shortDeclName type declName
|
||||
let fvar := mkFVar fvarId
|
||||
withReader (fun ctx => { ctx with lctx := lctx }) do
|
||||
withNewFVar fvar type k
|
||||
|
||||
/--
|
||||
Declare an auxiliary local declaration `shortDeclName : type` for elaborating recursive
|
||||
declaration `declName`, update the mapping `auxDeclToFullName`, and then execute `k`.
|
||||
-/
|
||||
def withAuxDecl (shortDeclName : Name) (type : Expr) (declName : Name) (k : Expr → n α) : n α :=
|
||||
map1MetaM (fun k => withAuxDeclImp shortDeclName type declName k) k
|
||||
|
||||
private def withNewBinderInfosImp (bs : Array (FVarId × BinderInfo)) (k : MetaM α) : MetaM α := do
|
||||
let lctx := bs.foldl (init := (← getLCtx)) fun lctx (fvarId, bi) =>
|
||||
lctx.setBinderInfo fvarId bi
|
||||
|
|
|
|||
|
|
@ -588,8 +588,15 @@ def instantiateMVars [Monad m] [MonadMCtx m] (e : Expr) : m Expr := do
|
|||
return r
|
||||
|
||||
def instantiateLCtxMVars [Monad m] [MonadMCtx m] (lctx : LocalContext) : m LocalContext :=
|
||||
let auxDeclToFullName := lctx.auxDeclToFullName
|
||||
lctx.foldlM (init := {}) fun lctx ldecl => do
|
||||
match ldecl with
|
||||
| .cdecl _ fvarId userName type _ .auxDecl =>
|
||||
let type ← instantiateMVars type
|
||||
let .some fullName := auxDeclToFullName.find? fvarId
|
||||
| panic! s!"Invalid auxiliary declaration found in local context: \
|
||||
{userName} does not have an associated full name."
|
||||
return lctx.mkAuxDecl fvarId userName type fullName
|
||||
| .cdecl _ fvarId userName type bi k =>
|
||||
let type ← instantiateMVars type
|
||||
return lctx.mkLocalDecl fvarId userName type bi k
|
||||
|
|
|
|||
|
|
@ -391,6 +391,170 @@ After `open Foo open Boo`, we have
|
|||
def resolveGlobalConstNoOverload [Monad m] [MonadResolveName m] [MonadEnv m] [MonadError m] (id : Syntax) : m Name := do
|
||||
ensureNonAmbiguous id (← resolveGlobalConst id)
|
||||
|
||||
/-- Resolves the name `n` in the local context. -/
|
||||
def resolveLocalName [Monad m] [MonadResolveName m] [MonadEnv m] [MonadLCtx m] (n : Name) : m (Option (Expr × List String)) := do
|
||||
let lctx ← getLCtx
|
||||
let auxDeclToFullName := (← getLCtx).auxDeclToFullName
|
||||
let currNamespace ← getCurrNamespace
|
||||
let view := extractMacroScopes n
|
||||
/- Simple case. "Match" function for regular local declarations. -/
|
||||
let matchLocalDecl? (localDecl : LocalDecl) (givenName : Name) : Option LocalDecl := do
|
||||
guard (localDecl.userName == givenName)
|
||||
return localDecl
|
||||
/-
|
||||
"Match" function for auxiliary declarations that correspond to recursive definitions being defined.
|
||||
This function is used in the first-pass.
|
||||
Note that we do not check for `localDecl.userName == givenName` in this pass as we do for regular local declarations.
|
||||
Reason: consider the following example
|
||||
```
|
||||
mutual
|
||||
inductive Foo
|
||||
| somefoo : Foo | bar : Bar → Foo → Foo
|
||||
inductive Bar
|
||||
| somebar : Bar| foobar : Foo → Bar → Bar
|
||||
end
|
||||
|
||||
mutual
|
||||
private def Foo.toString : Foo → String
|
||||
| Foo.somefoo => go 2 ++ toString.go 2 ++ Foo.toString.go 2
|
||||
| Foo.bar b f => toString f ++ Bar.toString b
|
||||
where
|
||||
go (x : Nat) := s!"foo {x}"
|
||||
|
||||
private def _root_.Ex2.Bar.toString : Bar → String
|
||||
| Bar.somebar => "bar"
|
||||
| Bar.foobar f b => Foo.toString f ++ Bar.toString b
|
||||
end
|
||||
```
|
||||
In the example above, we have two local declarations named `toString` in the local context, and
|
||||
we want the `toString f` to be resolved to `Foo.toString f`.
|
||||
-/
|
||||
let matchAuxRecDecl? (localDecl : LocalDecl) (fullDeclName : Name) (givenNameView : MacroScopesView) : Option LocalDecl := do
|
||||
let fullDeclView := extractMacroScopes fullDeclName
|
||||
/- First cleanup private name annotations -/
|
||||
let fullDeclView := { fullDeclView with name := (privateToUserName? fullDeclView.name).getD fullDeclView.name }
|
||||
let fullDeclName := fullDeclView.review
|
||||
let localDeclNameView := extractMacroScopes localDecl.userName
|
||||
/- If the current namespace is a prefix of the full declaration name,
|
||||
we use a relaxed matching test where we must satisfy the following conditions
|
||||
- The local declaration is a suffix of the given name.
|
||||
- The given name is a suffix of the full declaration.
|
||||
|
||||
Recall the `let rec`/`where` declaration naming convention. For example, suppose we have
|
||||
```
|
||||
def Foo.Bla.f ... :=
|
||||
... go ...
|
||||
where
|
||||
go ... := ...
|
||||
```
|
||||
The current namespace is `Foo.Bla`, and the full name for `go` is `Foo.Bla.f.g`, but we want to
|
||||
refer to it using just `go`. It is also accepted to refer to it using `f.go`, `Bla.f.go`, etc.
|
||||
|
||||
-/
|
||||
if currNamespace.isPrefixOf fullDeclName then
|
||||
/- Relaxed mode that allows us to access `let rec` declarations using shorter names -/
|
||||
guard (localDeclNameView.isSuffixOf givenNameView)
|
||||
guard (givenNameView.isSuffixOf fullDeclView)
|
||||
return localDecl
|
||||
else
|
||||
/-
|
||||
It is the standard algorithm we are using at `resolveGlobalName` for processing namespaces.
|
||||
|
||||
The current solution also has a limitation when using `def _root_` in a mutual block.
|
||||
The non `def _root_` declarations may update the namespace. See the following example:
|
||||
```
|
||||
mutual
|
||||
def Foo.f ... := ...
|
||||
def _root_.g ... := ...
|
||||
let rec h := ...
|
||||
...
|
||||
end
|
||||
```
|
||||
`def Foo.f` updates the namespace. Then, even when processing `def _root_.g ...`
|
||||
the condition `currNamespace.isPrefixOf fullDeclName` does not hold.
|
||||
This is not a big problem because we are planning to modify how we handle the mutual block in the future.
|
||||
|
||||
Note that we don't check for `localDecl.userName == givenName` here.
|
||||
-/
|
||||
let rec go (ns : Name) : Option LocalDecl := do
|
||||
if { givenNameView with name := ns ++ givenNameView.name }.review == fullDeclName then
|
||||
return localDecl
|
||||
match ns with
|
||||
| .str pre .. => go pre
|
||||
| _ => failure
|
||||
return (← go currNamespace)
|
||||
/- Traverse the local context backwards looking for match `givenNameView`.
|
||||
If `skipAuxDecl` we ignore `auxDecl` local declarations. -/
|
||||
let findLocalDecl? (givenNameView : MacroScopesView) (skipAuxDecl : Bool) : Option LocalDecl :=
|
||||
let givenName := givenNameView.review
|
||||
let localDecl? := lctx.decls.findSomeRev? fun localDecl? => do
|
||||
let localDecl ← localDecl?
|
||||
if localDecl.isAuxDecl then
|
||||
guard (!skipAuxDecl)
|
||||
if let some fullDeclName := auxDeclToFullName.find? localDecl.fvarId then
|
||||
matchAuxRecDecl? localDecl fullDeclName givenNameView
|
||||
else
|
||||
matchLocalDecl? localDecl givenName
|
||||
else
|
||||
matchLocalDecl? localDecl givenName
|
||||
if localDecl?.isSome || skipAuxDecl then
|
||||
localDecl?
|
||||
else
|
||||
-- Search auxDecls again trying an exact match of the given name
|
||||
lctx.decls.findSomeRev? fun localDecl? => do
|
||||
let localDecl ← localDecl?
|
||||
guard localDecl.isAuxDecl
|
||||
matchLocalDecl? localDecl givenName
|
||||
/-
|
||||
We use the parameter `globalDeclFound` to decide whether we should skip auxiliary declarations or not.
|
||||
We set it to true if we found a global declaration `n` as we iterate over the `loop`.
|
||||
Without this workaround, we would not be able to elaborate an example such as
|
||||
```
|
||||
def foo.aux := 1
|
||||
def foo : Nat → Nat
|
||||
| n => foo.aux -- should not be interpreted as `(foo).aux`
|
||||
```
|
||||
See test `aStructPerfIssue.lean` for another example.
|
||||
We skip auxiliary declarations when `projs` is not empty and `globalDeclFound` is true.
|
||||
Remark: we did not use to have the `globalDeclFound` parameter. Without this extra check we failed
|
||||
to elaborate
|
||||
```
|
||||
example : Nat :=
|
||||
let n := 0
|
||||
n.succ + (m |>.succ) + m.succ
|
||||
where
|
||||
m := 1
|
||||
```
|
||||
See issue #1850.
|
||||
-/
|
||||
let rec loop (n : Name) (projs : List String) (globalDeclFound : Bool) := do
|
||||
let givenNameView := { view with name := n }
|
||||
let mut globalDeclFoundNext := globalDeclFound
|
||||
unless globalDeclFound do
|
||||
let r ← resolveGlobalName givenNameView.review
|
||||
let r := r.filter fun (_, fieldList) => fieldList.isEmpty
|
||||
unless r.isEmpty do
|
||||
globalDeclFoundNext := true
|
||||
/-
|
||||
Note that we use `globalDeclFound` instead of `globalDeclFoundNext` in the following test.
|
||||
Reason: a local should shadow a global with the same name.
|
||||
Consider the following example. See issue #3079
|
||||
```
|
||||
def foo : Nat := 1
|
||||
|
||||
def bar : Nat :=
|
||||
foo.add 1 -- should be 11
|
||||
where
|
||||
foo := 10
|
||||
```
|
||||
-/
|
||||
match findLocalDecl? givenNameView (skipAuxDecl := globalDeclFound && !projs.isEmpty) with
|
||||
| some decl => return some (decl.toExpr, projs)
|
||||
| none => match n with
|
||||
| .str pre s => loop pre (s::projs) globalDeclFoundNext
|
||||
| _ => return none
|
||||
loop view.name [] (globalDeclFound := false)
|
||||
|
||||
/--
|
||||
Finds a name that unambiguously resolves to the given name `n₀`.
|
||||
Considers suffixes of `n₀` and suffixes of aliases of `n₀` when "unresolving".
|
||||
|
|
@ -402,10 +566,14 @@ When `allowHorizAliases` is false, then "horizontal aliases" (ones that are not
|
|||
The assumption is that non-horizontal aliases are "API exports" (i.e., intentional exports that should be considered to be the new canonical name).
|
||||
"Non-API exports" arise from (1) using `export` to add names to a namespace for dot notation or (2) projects that want names to be conveniently and permanently accessible in their own namespaces.
|
||||
|
||||
`filter` specifies a predicate that the unresolved name must additionally satisfy.
|
||||
|
||||
This function is meant to be used for pretty printing.
|
||||
If `n₀` is an accessible name, then the result will be an accessible name.
|
||||
-/
|
||||
def unresolveNameGlobal [Monad m] [MonadResolveName m] [MonadEnv m] (n₀ : Name) (fullNames := false) (allowHorizAliases := false) : m Name := do
|
||||
def unresolveNameGlobal [Monad m] [MonadResolveName m] [MonadEnv m]
|
||||
(n₀ : Name) (fullNames := false) (allowHorizAliases := false)
|
||||
(filter : Name → m Bool := fun _ => pure true) : m Name := do
|
||||
if n₀.hasMacroScopes then return n₀
|
||||
if fullNames then
|
||||
match (← resolveGlobalName n₀) with
|
||||
|
|
@ -428,18 +596,13 @@ where
|
|||
candidate := Name.appendCore cmpt candidate
|
||||
if let [(potentialMatch, _)] ← resolveGlobalName candidate then
|
||||
if potentialMatch == n₀ then
|
||||
return some candidate
|
||||
if (← filter candidate) then
|
||||
return some candidate
|
||||
return none
|
||||
|
||||
def unresolveNameGlobalAvoidingLocals [Monad m] [MonadResolveName m] [MonadEnv m] [MonadLCtx m]
|
||||
(n₀ : Name) (fullNames := false) : m Name := do
|
||||
let mut n ← unresolveNameGlobal n₀ fullNames
|
||||
unless (← getLCtx).usesUserName n do return n
|
||||
-- `n` is also a local declaration
|
||||
if n == n₀ then
|
||||
-- `n` is the fully qualified name. So, we append the `_root_` prefix
|
||||
return `_root_ ++ n
|
||||
else
|
||||
return n₀
|
||||
/-- Like `Lean.unresolveNameGlobal`, but also ensures that the unresolved name does not conflict
|
||||
with the names of any local declarations. -/
|
||||
def unresolveNameGlobalAvoidingLocals [Monad m] [MonadResolveName m] [MonadEnv m] [MonadLCtx m] (n₀ : Name) (fullNames := false) : m Name :=
|
||||
unresolveNameGlobal n₀ (fullNames := fullNames) (filter := fun n => Option.isNone <$> resolveLocalName n)
|
||||
|
||||
end Lean
|
||||
|
|
|
|||
22
tests/lean/run/6706.lean
Normal file
22
tests/lean/run/6706.lean
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/-!
|
||||
# `simp?`-suggested names conflicting with auxiliary declarations
|
||||
|
||||
https://github.com/leanprover/lean4/issues/6706
|
||||
|
||||
This test ensures that "unresolved" names provided by `simp?` do not conflict with local auxiliary
|
||||
declarations.
|
||||
-/
|
||||
|
||||
def P := True
|
||||
theorem N.A.B : P := trivial
|
||||
/-- info: Try this: simp only [N.A.B] -/
|
||||
#guard_msgs in
|
||||
theorem N.X.A.B : P := by
|
||||
simp? [N.A.B]
|
||||
|
||||
/-- info: Try this: simp only [_root_.N.A.B] -/
|
||||
#guard_msgs in
|
||||
theorem A : P :=
|
||||
let rec N.A.B := ()
|
||||
by simp? [_root_.N.A.B]
|
||||
where B := ()
|
||||
25
tests/lean/run/7073.lean
Normal file
25
tests/lean/run/7073.lean
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/-!
|
||||
# Auxiliary declaration name resolution in tactic blocks
|
||||
|
||||
https://github.com/leanprover/lean4/issues/7073
|
||||
|
||||
It should be possible to refer to auxiliary declarations by partially- or fully-qualified names
|
||||
in tactic blocks.
|
||||
-/
|
||||
|
||||
theorem A : True ∧ True := by
|
||||
let rec B := trivial
|
||||
exact And.intro A.B A.C
|
||||
where C := trivial
|
||||
|
||||
namespace M
|
||||
|
||||
theorem N : True ∧ True ∧ True :=
|
||||
let rec O := by exact M.N.P
|
||||
by exact ⟨N.O, M.N.O, N.P⟩
|
||||
where P := trivial
|
||||
|
||||
theorem Q.R : Nat → True
|
||||
| 0 => trivial
|
||||
| 1 => by exact Q.R 0
|
||||
| n + 2 => by exact M.Q.R (n + 1)
|
||||
|
|
@ -2,7 +2,7 @@ import Lean
|
|||
set_option maxHeartbeats 100000
|
||||
attribute [simp] Array.findIdx?.loop
|
||||
attribute [simp] Lean.expandExplicitBindersAux.loop
|
||||
attribute [simp] Lean.Elab.Term.resolveLocalName.loop
|
||||
attribute [simp] Lean.resolveLocalName.loop
|
||||
|
||||
|
||||
-- Mathlib
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue