This PR changes macro scope numbering from per-module to per-command, ensuring that unrelated changes to other commands do not affect macro scopes generated by a command, which improves `prefer_native` hit rates on bootstrapping as well as avoids further rebuilds under the module system. In detail, instead of always using the current module name as a macro scope prefix, each command now introduces a new macro scope prefix (called "context") of the shape `<main module>._hygCtx_<uniq>` where `uniq` is a `UInt32` derived from the command but automatically incremented in case of conflicts (which must be local to the current module). In the current implementation, `uniq` is the hash of the declaration name, if any, or else the hash of the full command's syntax. Thus, it is always independent of syntactic changes to other commands (except in case of hash conflicts, which should only happen in practice for syntactically identical commands) and, in the case of declarations, also independent of syntactic changes to any private parts of the declaration.
119 lines
4.6 KiB
Text
119 lines
4.6 KiB
Text
/-
|
||
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
|
||
Released under Apache 2.0 license as described in the file LICENSE.
|
||
Authors: Sebastian Ullrich
|
||
-/
|
||
module
|
||
|
||
prelude
|
||
public import Lean.Data.Name
|
||
public import Lean.Data.Options
|
||
public import Lean.Data.Format
|
||
|
||
public section
|
||
|
||
namespace Lean
|
||
/-! Remark: `MonadQuotation` class is part of the `Init` package and loaded by default since it is used in the builtin command `macro`. -/
|
||
|
||
structure Unhygienic.Context where
|
||
ref : Syntax
|
||
scope : MacroScope
|
||
|
||
/--
|
||
Simplistic MonadQuotation that does not guarantee globally fresh names, that
|
||
is, between different runs of this or other MonadQuotation implementations.
|
||
It is only safe if the syntax quotations do not introduce bindings around
|
||
antiquotations, and if references to globals are prefixed with `_root_.`
|
||
(which is not allowed to refer to a local variable)
|
||
`Unhygienic` can also be seen as a model implementation of `MonadQuotation`
|
||
(since it is completely hygienic as long as it is "run" only once and can
|
||
assume that there are no other implementations in use, as is the case for the
|
||
elaboration monads that carry their macro scope state through the entire
|
||
processing of a file). It uses the state monad to query and allocate the
|
||
next macro scope, and uses the reader monad to store the stack of scopes
|
||
corresponding to `withFreshMacroScope` calls.
|
||
-/
|
||
abbrev Unhygienic := ReaderT Lean.Unhygienic.Context <| StateM MacroScope
|
||
namespace Unhygienic
|
||
instance : MonadQuotation Unhygienic where
|
||
getRef := return (← read).ref
|
||
withRef := fun ref => withReader ({ · with ref := ref })
|
||
getCurrMacroScope := return (← read).scope
|
||
getContext := pure `UnhygienicMain
|
||
withFreshMacroScope := fun x => do
|
||
let fresh ← modifyGet fun n => (n, n + 1)
|
||
withReader ({ · with scope := fresh}) x
|
||
|
||
@[inline]
|
||
protected def run {α : Type} (x : Unhygienic α) : α := (x ⟨Syntax.missing, firstFrontendMacroScope⟩).run' (firstFrontendMacroScope+1)
|
||
end Unhygienic
|
||
|
||
private def mkInaccessibleUserNameAux (unicode : Bool) (name : Name) (idx : Nat) : Name :=
|
||
if unicode then
|
||
if idx == 0 then
|
||
name.appendAfter "✝"
|
||
else
|
||
name.appendAfter ("✝" ++ idx.toSuperscriptString)
|
||
else
|
||
name ++ Name.num `_inaccessible idx
|
||
|
||
private def mkInaccessibleUserName (unicode : Bool) : Name → Name
|
||
| .num p@(.str ..) idx =>
|
||
mkInaccessibleUserNameAux unicode p idx
|
||
| .num .anonymous idx =>
|
||
mkInaccessibleUserNameAux unicode Name.anonymous idx
|
||
| .num p idx =>
|
||
if unicode then
|
||
(mkInaccessibleUserName unicode p).appendAfter ("⁻" ++ idx.toSuperscriptString)
|
||
else
|
||
Name.mkNum (mkInaccessibleUserName unicode p) idx
|
||
| n => n
|
||
|
||
register_builtin_option pp.sanitizeNames : Bool := {
|
||
defValue := true
|
||
group := "pp"
|
||
descr := "add suffix to shadowed/inaccessible variables when pretty printing"
|
||
}
|
||
|
||
def getSanitizeNames (o : Options) : Bool := pp.sanitizeNames.get o
|
||
|
||
structure NameSanitizerState where
|
||
options : Options
|
||
/-- `x` ~> 2 if we're already using `x✝`, `x✝¹` -/
|
||
nameStem2Idx : NameMap Nat := {}
|
||
/-- `x._hyg...` ~> `x✝` -/
|
||
userName2Sanitized : NameMap Name := {}
|
||
|
||
private partial def mkFreshInaccessibleUserName (userName : Name) (idx : Nat) : StateM NameSanitizerState Name := do
|
||
let s ← get
|
||
let userNameNew := mkInaccessibleUserName (Std.Format.getUnicode s.options) (Name.mkNum userName idx)
|
||
if s.nameStem2Idx.contains userNameNew then
|
||
mkFreshInaccessibleUserName userName (idx+1)
|
||
else do
|
||
modify fun s => { s with nameStem2Idx := s.nameStem2Idx.insert userName (idx+1) }
|
||
pure userNameNew
|
||
|
||
/-- Erase macro scopes from `userName` and add "tombstone" + superscript (or `._hyg`). -/
|
||
def sanitizeName (userName : Name) : StateM NameSanitizerState Name := do
|
||
let stem := userName.eraseMacroScopes;
|
||
let idx := (← get).nameStem2Idx.find? stem |>.getD 0
|
||
let san ← mkFreshInaccessibleUserName stem idx
|
||
modify fun s => { s with userName2Sanitized := s.userName2Sanitized.insert userName san }
|
||
pure san
|
||
|
||
private partial def sanitizeSyntaxAux : Syntax → StateM NameSanitizerState Syntax
|
||
| stx@(Syntax.ident _ _ n _) => do
|
||
let n ← match (← get).userName2Sanitized.find? n with
|
||
| some n' => pure n'
|
||
| none => if n.hasMacroScopes then sanitizeName n else pure n
|
||
return mkIdentFrom stx n
|
||
| Syntax.node info k args => Syntax.node info k <$> args.mapM sanitizeSyntaxAux
|
||
| stx => pure stx
|
||
|
||
def sanitizeSyntax (stx : Syntax) : StateM NameSanitizerState Syntax := do
|
||
if getSanitizeNames (← get).options then
|
||
sanitizeSyntaxAux stx
|
||
else
|
||
pure stx
|
||
|
||
end Lean
|