/- Copyright (c) 2020 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Leonardo de Moura, Sebastian Ullrich -/ import Lean.Compiler.InitAttr import Lean.ScopedEnvExtension import Lean.Compiler.IR.CompilerM /-! A builder for attributes that are applied to declarations of a common type and group them by the given attribute argument (an arbitrary `Name`, currently). Also creates a second "builtin" attribute used for bootstrapping, which saves the applied declarations in an `IO.Ref` instead of an environment extension. Used to register elaborators, macros, tactics, and delaborators. -/ namespace Lean namespace KeyedDeclsAttribute -- could be a parameter as well, but right now it's all names abbrev Key := Name /-- `KeyedDeclsAttribute` definition. Important: `mkConst valueTypeName` and `γ` must be definitionally equal. -/ structure Def (γ : Type) where /-- Builtin attribute name, if any (e.g., `builtin_term_elab) -/ builtinName : Name := Name.anonymous /-- Attribute name (e.g., `term_elab) -/ name : Name /-- Attribute description -/ descr : String valueTypeName : Name /-- Convert `Syntax` into a `Key`, the default implementation expects an identifier. -/ evalKey (builtin : Bool) (stx : Syntax) : AttrM Key := do let stx ← Attribute.Builtin.getIdent stx let kind := stx.getId if (← getEnv).contains kind && (← Elab.getInfoState).enabled then Elab.addConstInfo stx kind none pure kind onAdded (builtin : Bool) (declName : Name) : AttrM Unit := pure () deriving Inhabited structure OLeanEntry where key : Key /-- Name of a declaration stored in the environment which has type `mkConst Def.valueTypeName`. -/ declName : Name deriving Inhabited structure AttributeEntry (γ : Type) extends OLeanEntry where /-- Recall that we cannot store `γ` into .olean files because it is a closure. Given `OLeanEntry.declName`, we convert it into a `γ` by using the unsafe function `evalConstCheck`. -/ value : γ abbrev Table (γ : Type) := SMap Key (List (AttributeEntry γ)) structure ExtensionState (γ : Type) where newEntries : List OLeanEntry := [] table : Table γ := {} declNames : PHashSet Name := {} erased : PHashSet Name := {} deriving Inhabited abbrev Extension (γ : Type) := ScopedEnvExtension OLeanEntry (AttributeEntry γ) (ExtensionState γ) end KeyedDeclsAttribute structure KeyedDeclsAttribute (γ : Type) where defn : KeyedDeclsAttribute.Def γ -- imported/builtin instances tableRef : IO.Ref (KeyedDeclsAttribute.Table γ) -- instances from current module ext : KeyedDeclsAttribute.Extension γ deriving Nonempty namespace KeyedDeclsAttribute private def Table.insert (table : Table γ) (v : AttributeEntry γ) : Table γ := match table.find? v.key with | some vs => SMap.insert table v.key (v::vs) | none => SMap.insert table v.key [v] def ExtensionState.insert (s : ExtensionState γ) (v : AttributeEntry γ) : ExtensionState γ := { table := s.table.insert v newEntries := v.toOLeanEntry :: s.newEntries declNames := s.declNames.insert v.declName erased := s.erased.erase v.declName } def addBuiltin (attr : KeyedDeclsAttribute γ) (key : Key) (declName : Name) (value : γ) : IO Unit := attr.tableRef.modify fun m => m.insert { key, declName, value } def mkStateOfTable (table : Table γ) : ExtensionState γ := { table declNames := table.fold (init := {}) fun s _ es => es.foldl (init := s) fun s e => s.insert e.declName } def ExtensionState.erase (s : ExtensionState γ) (attrName : Name) (declName : Name) : CoreM (ExtensionState γ) := do unless s.declNames.contains declName do throwError "'{declName}' does not have [{attrName}] attribute" return { s with erased := s.erased.insert declName, declNames := s.declNames.erase declName } protected unsafe def init {γ} (df : Def γ) (attrDeclName : Name := by exact decl_name%) : IO (KeyedDeclsAttribute γ) := do let tableRef ← IO.mkRef ({} : Table γ) let ext : Extension γ ← registerScopedEnvExtension { name := attrDeclName mkInitial := return mkStateOfTable (← tableRef.get) ofOLeanEntry := fun _ entry => do let ctx ← read match ctx.env.evalConstCheck γ ctx.opts df.valueTypeName entry.declName with | Except.ok f => return { toOLeanEntry := entry, value := f } | Except.error ex => throw (IO.userError ex) addEntry := fun s e => s.insert e toOLeanEntry := (·.toOLeanEntry) } unless df.builtinName.isAnonymous do registerBuiltinAttribute { ref := attrDeclName name := df.builtinName descr := "(builtin) " ++ df.descr add := fun declName stx kind => do unless kind == AttributeKind.global do throwError "invalid attribute '{df.builtinName}', must be global" let key ← df.evalKey true stx let decl ← getConstInfo declName match decl.type with | Expr.const c _ => if c != df.valueTypeName then throwError "unexpected type at '{declName}', '{df.valueTypeName}' expected" else /- builtin_initialize @addBuiltin $(mkConst valueTypeName) $(mkConst attrDeclName) $(key) $(declName) $(mkConst declName) -/ let val := mkAppN (mkConst ``addBuiltin) #[mkConst df.valueTypeName, mkConst attrDeclName, toExpr key, toExpr declName, mkConst declName] declareBuiltin declName val df.onAdded true declName | _ => throwError "unexpected type at '{declName}', '{df.valueTypeName}' expected" applicationTime := AttributeApplicationTime.afterCompilation } registerBuiltinAttribute { ref := attrDeclName name := df.name descr := df.descr erase := fun declName => do let s := ext.getState (← getEnv) let s ← s.erase df.name declName modifyEnv fun env => ext.modifyState env fun _ => s add := fun declName stx attrKind => do let key ← df.evalKey false stx match IR.getSorryDep (← getEnv) declName with | none => let val ← evalConstCheck γ df.valueTypeName declName ext.add { key := key, declName := declName, value := val } attrKind df.onAdded false declName | _ => -- If the declaration contains `sorry`, we skip `evalConstCheck` to avoid unnecessary bizarre error message pure () applicationTime := AttributeApplicationTime.afterCompilation } pure { defn := df, tableRef := tableRef, ext := ext } /-- Retrieve entries tagged with `[attr key]` or `[builtinAttr key]`. -/ def getEntries {γ} (attr : KeyedDeclsAttribute γ) (env : Environment) (key : Name) : List (AttributeEntry γ) := let s := attr.ext.getState env let attrs := s.table.findD key [] if s.erased.isEmpty then attrs else attrs.filter fun attr => !s.erased.contains attr.declName /-- Retrieve values tagged with `[attr key]` or `[builtinAttr key]`. -/ def getValues {γ} (attr : KeyedDeclsAttribute γ) (env : Environment) (key : Name) : List γ := (getEntries attr env key).map AttributeEntry.value end KeyedDeclsAttribute end Lean