lean4-htt/src/Lean/ResolveName.lean
jrr6 e337129108
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.
2025-03-03 16:10:54 +00:00

608 lines
26 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Sebastian Ullrich
-/
prelude
import Lean.Data.OpenDecl
import Lean.Hygiene
import Lean.Modifiers
import Lean.Exception
namespace Lean
/-!
Reserved names.
We use reserved names for automatically generated theorems (e.g., equational theorems).
Automation may register new reserved name predicates.
In this module, we just check the registered predicates, but do not trigger actions associated with them.
For example, give a definition `foo`, we flag `foo.def` as reserved symbol.
-/
def throwReservedNameNotAvailable [Monad m] [MonadError m] (declName : Name) (reservedName : Name) : m Unit := do
throwError "failed to declare `{declName}` because `{.ofConstName reservedName true}` has already been declared"
def ensureReservedNameAvailable [Monad m] [MonadEnv m] [MonadError m] (declName : Name) (suffix : String) : m Unit := do
let reservedName := .str declName suffix
if (← getEnv).contains reservedName then
throwReservedNameNotAvailable declName reservedName
/-- Global reference containing all reserved name predicates. -/
builtin_initialize reservedNamePredicatesRef : IO.Ref (Array (Environment → Name → Bool)) ← IO.mkRef #[]
/--
Registers a new reserved name predicate.
-/
def registerReservedNamePredicate (p : Environment → Name → Bool) : IO Unit := do
unless (← initializing) do
throw (IO.userError "failed to register reserved name suffix predicate, this operation can only be performed during initialization")
reservedNamePredicatesRef.modify fun ps => ps.push p
builtin_initialize reservedNamePredicatesExt : EnvExtension (Array (Environment → Name → Bool)) ←
registerEnvExtension reservedNamePredicatesRef.get
/--
Returns `true` if `name` is a reserved name.
-/
def isReservedName (env : Environment) (name : Name) : Bool :=
reservedNamePredicatesExt.getState env |>.any (· env name)
/-!
We use aliases to implement the `export <id> (<id>+)` command.
An `export A (x)` in the namespace `B` produces an alias `B.x ~> A.x`.
-/
abbrev AliasState := SMap Name (List Name)
abbrev AliasEntry := Name × Name
def addAliasEntry (s : AliasState) (e : AliasEntry) : AliasState :=
match s.find? e.1 with
| none => s.insert e.1 [e.2]
| some es => if es.contains e.2 then s else s.insert e.1 (e.2 :: es)
builtin_initialize aliasExtension : SimplePersistentEnvExtension AliasEntry AliasState ←
registerSimplePersistentEnvExtension {
addEntryFn := addAliasEntry,
addImportedFn := fun es => mkStateFromImportedEntries addAliasEntry {} es |>.switch
}
/-- Add alias `a` for `e` -/
@[export lean_add_alias] def addAlias (env : Environment) (a : Name) (e : Name) : Environment :=
aliasExtension.addEntry env (a, e)
def getAliasState (env : Environment) : AliasState :=
aliasExtension.getState env
/--
Retrieve aliases for `a`. If `skipProtected` is `true`, then the resulting list only includes
declarations that are not marked as `protected`.
-/
def getAliases (env : Environment) (a : Name) (skipProtected : Bool) : List Name :=
match aliasExtension.getState env |>.find? a with
| none => []
| some es =>
if skipProtected then
es.filter (!isProtected env ·)
else
es
-- slower, but only used in the pretty printer
def getRevAliases (env : Environment) (e : Name) : List Name :=
(aliasExtension.getState env).fold (fun as a es => if List.contains es e then a :: as else as) []
/-! # Global name resolution -/
namespace ResolveName
private def containsDeclOrReserved (env : Environment) (declName : Name) : Bool :=
env.contains declName || isReservedName env declName
/-- Check whether `ns ++ id` is a valid namespace name and/or there are aliases names `ns ++ id`. -/
private def resolveQualifiedName (env : Environment) (ns : Name) (id : Name) : List Name :=
let resolvedId := ns ++ id
-- We ignore protected aliases if `id` is atomic.
let resolvedIds := getAliases env resolvedId (skipProtected := id.isAtomic)
if (containsDeclOrReserved env resolvedId && (!id.isAtomic || !isProtected env resolvedId)) then
resolvedId :: resolvedIds
else
-- Check whether environment contains the private version. That is, `_private.<module_name>.ns.id`.
let resolvedIdPrv := mkPrivateName env resolvedId
if containsDeclOrReserved env resolvedIdPrv then resolvedIdPrv :: resolvedIds
else resolvedIds
/-- Check surrounding namespaces -/
private def resolveUsingNamespace (env : Environment) (id : Name) : Name → List Name
| ns@(.str p _) =>
match resolveQualifiedName env ns id with
| [] => resolveUsingNamespace env id p
| resolvedIds => resolvedIds
| _ => []
/-- Check exact name -/
private def resolveExact (env : Environment) (id : Name) : Option Name :=
if id.isAtomic then none
else
let resolvedId := id.replacePrefix rootNamespace Name.anonymous
if containsDeclOrReserved env resolvedId then some resolvedId
else
-- We also allow `_root` when accessing private declarations.
-- If we change our minds, we should just replace `resolvedId` with `id`
let resolvedIdPrv := mkPrivateName env resolvedId
if containsDeclOrReserved env resolvedIdPrv then some resolvedIdPrv
else none
/-- Check `OpenDecl`s -/
private def resolveOpenDecls (env : Environment) (id : Name) : List OpenDecl → List Name → List Name
| [], resolvedIds => resolvedIds
| OpenDecl.simple ns exs :: openDecls, resolvedIds =>
if exs.contains id then
resolveOpenDecls env id openDecls resolvedIds
else
let newResolvedIds := resolveQualifiedName env ns id
resolveOpenDecls env id openDecls (newResolvedIds ++ resolvedIds)
| OpenDecl.explicit openedId resolvedId :: openDecls, resolvedIds =>
let resolvedIds :=
if openedId == id then
resolvedId :: resolvedIds
else if openedId.isPrefixOf id then
let candidate := id.replacePrefix openedId resolvedId
if env.contains candidate then
candidate :: resolvedIds
else
resolvedIds
else
resolvedIds
resolveOpenDecls env id openDecls resolvedIds
/--
Primitive global name resolution procedure. It does not trigger actions associated with reserved names.
Recall that Lean has reserved names. For example, a definition `foo` has a reserved name `foo.def` for theorem
containing stating that `foo` is equal to its definition. The action associated with `foo.def`
automatically proves the theorem. At the macro level, the name is resolved, but the action is not
executed.
-/
def resolveGlobalName (env : Environment) (ns : Name) (openDecls : List OpenDecl) (id : Name) : List (Name × List String) :=
-- decode macro scopes from name before recursion
let extractionResult := extractMacroScopes id
let rec loop (id : Name) (projs : List String) : List (Name × List String) :=
match id with
| .str p s =>
-- NOTE: we assume that macro scopes always belong to the projected constant, not the projections
let id := { extractionResult with name := id }.review
match resolveUsingNamespace env id ns with
| resolvedIds@(_ :: _) => resolvedIds.eraseDups.map fun id => (id, projs)
| [] =>
match resolveExact env id with
| some newId => [(newId, projs)]
| none =>
let resolvedIds := if containsDeclOrReserved env id then [id] else []
let idPrv := mkPrivateName env id
let resolvedIds := if containsDeclOrReserved env idPrv then [idPrv] ++ resolvedIds else resolvedIds
let resolvedIds := resolveOpenDecls env id openDecls resolvedIds
let resolvedIds := getAliases env id (skipProtected := id.isAtomic) ++ resolvedIds
match resolvedIds with
| _ :: _ => resolvedIds.eraseDups.map fun id => (id, projs)
| [] => loop p (s::projs)
| _ => []
loop extractionResult.name []
/-! # Namespace resolution -/
def resolveNamespaceUsingScope? (env : Environment) (n : Name) (ns : Name) : Option Name :=
match ns with
| .str p _ =>
if env.isNamespace (ns ++ n) then
some (ns ++ n)
else
resolveNamespaceUsingScope? env n p
| .anonymous =>
let n := n.replacePrefix rootNamespace .anonymous
if env.isNamespace n then some n else none
| _ => unreachable!
def resolveNamespaceUsingOpenDecls (env : Environment) (n : Name) : List OpenDecl → List Name
| [] => []
| OpenDecl.simple ns exs :: ds =>
if env.isNamespace (ns ++ n) && !exs.contains n then
(ns ++ n) :: resolveNamespaceUsingOpenDecls env n ds
else
resolveNamespaceUsingOpenDecls env n ds
| _ :: ds => resolveNamespaceUsingOpenDecls env n ds
/--
Given a name `id` try to find namespaces it may refer to. The resolution procedure works as follows
1- If `id` is in the scope of `namespace` commands the namespace `s_1. ... . s_n`,
then we include `s_1 . ... . s_i ++ n` in the result if it is the name of an existing namespace.
We search "backwards", and include at most one of the in the list of resulting namespaces.
2- If `id` is the exact name of an existing namespace, then include `id`
3- Finally, for each command `open N`, include in the result `N ++ n` if it is the name of an existing namespace.
We only consider simple `open` commands. -/
def resolveNamespace (env : Environment) (ns : Name) (openDecls : List OpenDecl) (id : Name) : List Name :=
match resolveNamespaceUsingScope? env id ns with
| some ns => ns :: resolveNamespaceUsingOpenDecls env id openDecls
| none => resolveNamespaceUsingOpenDecls env id openDecls
end ResolveName
class MonadResolveName (m : Type → Type) where
getCurrNamespace : m Name
getOpenDecls : m (List OpenDecl)
export MonadResolveName (getCurrNamespace getOpenDecls)
instance (m n) [MonadLift m n] [MonadResolveName m] : MonadResolveName n where
getCurrNamespace := liftM (m:=m) getCurrNamespace
getOpenDecls := liftM (m:=m) getOpenDecls
/--
Given a name `n`, return a list of possible interpretations.
Each interpretation is a pair `(declName, fieldList)`, where `declName`
is the name of a declaration in the current environment, and `fieldList` are
(potential) field names.
The pair is needed because in Lean `.` may be part of a qualified name or
a field (aka dot-notation).
As an example, consider the following definitions
```
def Boo.x := 1
def Foo.x := 2
def Foo.x.y := 3
```
After `open Foo`, we have
- `resolveGlobalName x` => `[(Foo.x, [])]`
- `resolveGlobalName x.y` => `[(Foo.x.y, [])]`
- `resolveGlobalName x.z.w` => `[(Foo.x, [z, w])]`
After `open Foo open Boo`, we have
- `resolveGlobalName x` => `[(Foo.x, []), (Boo.x, [])]`
- `resolveGlobalName x.y` => `[(Foo.x.y, [])]`
- `resolveGlobalName x.z.w` => `[(Foo.x, [z, w]), (Boo.x, [z, w])]`
-/
def resolveGlobalName [Monad m] [MonadResolveName m] [MonadEnv m] (id : Name) : m (List (Name × List String)) := do
return ResolveName.resolveGlobalName (← getEnv) (← getCurrNamespace) (← getOpenDecls) id
/--
Given a namespace name, return a list of possible interpretations.
Names extracted from syntax should be passed to `resolveNamespace` instead.
-/
def resolveNamespaceCore [Monad m] [MonadResolveName m] [MonadEnv m] [MonadError m] (id : Name) (allowEmpty := false) : m (List Name) := do
let nss := ResolveName.resolveNamespace (← getEnv) (← getCurrNamespace) (← getOpenDecls) id
if !allowEmpty && nss.isEmpty then
throwError s!"unknown namespace '{id}'"
return nss
/-- Given a namespace identifier, return a list of possible interpretations. -/
def resolveNamespace [Monad m] [MonadResolveName m] [MonadEnv m] [MonadError m] : Ident → m (List Name)
| stx@⟨Syntax.ident _ _ n pre⟩ => do
let pre := pre.filterMap fun
| .namespace ns => some ns
| _ => none
if pre.isEmpty then
withRef stx <| resolveNamespaceCore n
else
return pre
| stx => throwErrorAt stx s!"expected identifier"
/-- Given a namespace identifier, return the unique interpretation or else fail. -/
def resolveUniqueNamespace [Monad m] [MonadResolveName m] [MonadEnv m] [MonadError m] (id : Ident) : m Name := do
match (← resolveNamespace id) with
| [ns] => return ns
| nss => throwError s!"ambiguous namespace '{id.getId}', possible interpretations: '{nss}'"
/-- Helper function for `resolveGlobalConstCore`. -/
def filterFieldList [Monad m] [MonadError m] (n : Name) (cs : List (Name × List String)) : m (List Name) := do
let cs := cs.filter fun (_, fieldList) => fieldList.isEmpty
if cs.isEmpty then throwUnknownConstant n
return cs.map (·.1)
/-- Given a name `n`, returns a list of possible interpretations for global constants.
Similar to `resolveGlobalName`, but discard any candidate whose `fieldList` is not empty.
For identifiers taken from syntax, use `resolveGlobalConst` instead, which respects preresolved names. -/
private def resolveGlobalConstCore [Monad m] [MonadResolveName m] [MonadEnv m] [MonadError m] (n : Name) : m (List Name) := do
let cs ← resolveGlobalName n
filterFieldList n cs
/-- Helper function for `resolveGlobalConstNoOverloadCore` -/
def ensureNoOverload [Monad m] [MonadError m] (n : Name) (cs : List Name) : m Name := do
match cs with
| [c] => pure c
| _ => throwError s!"ambiguous identifier '{mkConst n}', possible interpretations: {cs.map mkConst}"
/-- For identifiers taken from syntax, use `resolveGlobalConstNoOverload` instead, which respects preresolved names. -/
def resolveGlobalConstNoOverloadCore [Monad m] [MonadResolveName m] [MonadEnv m] [MonadError m] (n : Name) : m Name := do
ensureNoOverload n (← resolveGlobalConstCore n)
def preprocessSyntaxAndResolve [Monad m] [MonadError m] (stx : Syntax) (k : Name → m (List Name)) : m (List Name) := do
match stx with
| .ident _ _ n pre => do
let pre := pre.filterMap fun
| .decl n [] => some n
| _ => none
if pre.isEmpty then
withRef stx <| k n
else
return pre
| _ => throwErrorAt stx s!"expected identifier"
/--
Interprets the syntax `n` as an identifier for a global constant, and returns a list of resolved
constant names that it could be referring to based on the currently open namespaces.
This should be used instead of `resolveGlobalConstCore` for identifiers taken from syntax
because `Syntax` objects may have names that have already been resolved.
Consider using `realizeGlobalConst` if you need to handle reserved names, and
`realizeGlobalConstWithInfo` if you want the syntax to show the resulting name's info on hover.
## Example:
```
def Boo.x := 1
def Foo.x := 2
def Foo.x.y := 3
```
After `open Foo`, we have
- `resolveGlobalConst x` => `[Foo.x]`
- `resolveGlobalConst x.y` => `[Foo.x.y]`
- `resolveGlobalConst x.z.w` => error: unknown constant
After `open Foo open Boo`, we have
- `resolveGlobalConst x` => `[Foo.x, Boo.x]`
- `resolveGlobalConst x.y` => `[Foo.x.y]`
- `resolveGlobalConst x.z.w` => error: unknown constant
-/
def resolveGlobalConst [Monad m] [MonadResolveName m] [MonadEnv m] [MonadError m] (stx : Syntax) : m (List Name) :=
preprocessSyntaxAndResolve stx resolveGlobalConstCore
/--
Given a list of names produced by `resolveGlobalConst`, throws an error if the list does not contain
exactly one element.
Recall that `resolveGlobalConst` does not return empty lists.
-/
def ensureNonAmbiguous [Monad m] [MonadError m] (id : Syntax) (cs : List Name) : m Name := do
match cs with
| [] => unreachable!
| [c] => pure c
| _ => throwErrorAt id s!"ambiguous identifier '{id}', possible interpretations: {cs.map mkConst}"
/-- Interprets the syntax `n` as an identifier for a global constant, and return a resolved
constant name. If there are multiple possible interpretations it will throw.
Consider using `realizeGlobalConstNoOverload` if you need to handle reserved names, and
`realizeGlobalConstNoOverloadWithInfo` if you
want the syntax to show the resulting name's info on hover.
## Example:
```
def Boo.x := 1
def Foo.x := 2
def Foo.x.y := 3
```
After `open Foo`, we have
- `resolveGlobalConstNoOverload x` => `Foo.x`
- `resolveGlobalConstNoOverload x.y` => `Foo.x.y`
- `resolveGlobalConstNoOverload x.z.w` => error: unknown constant
After `open Foo open Boo`, we have
- `resolveGlobalConstNoOverload x` => error: ambiguous identifier
- `resolveGlobalConstNoOverload x.y` => `Foo.x.y`
- `resolveGlobalConstNoOverload x.z.w` => error: unknown constant
-/
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".
Aliases are considered first.
When `fullNames` is true, returns either `n₀` or `_root_.n₀`.
When `allowHorizAliases` is false, then "horizontal aliases" (ones that are not put into a parent namespace) are filtered out.
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)
(filter : Name → m Bool := fun _ => pure true) : m Name := do
if n₀.hasMacroScopes then return n₀
if fullNames then
match (← resolveGlobalName n₀) with
| [(potentialMatch, _)] => if (privateToUserName? potentialMatch).getD potentialMatch == n₀ then return n₀ else return rootNamespace ++ n₀
| _ => return n₀ -- if can't resolve, return the original
let mut initialNames := (getRevAliases (← getEnv) n₀).toArray
unless allowHorizAliases do
initialNames := initialNames.filter fun n => n.getPrefix.isPrefixOf n₀.getPrefix
initialNames := initialNames.push (rootNamespace ++ n₀)
for initialName in initialNames do
if let some n ← unresolveNameCore initialName then
return n
return n₀ -- if can't resolve, return the original
where
unresolveNameCore (n : Name) : m (Option Name) := do
if n.hasMacroScopes then return none
let mut revComponents := n.componentsRev
let mut candidate := Name.anonymous
for cmpt in revComponents do
candidate := Name.appendCore cmpt candidate
if let [(potentialMatch, _)] ← resolveGlobalName candidate then
if potentialMatch == n₀ then
if (← filter candidate) then
return some candidate
return none
/-- 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