lean4-htt/src/Lean/Elab/DeclModifiers.lean
Leonardo de Moura 4606c35c40
feat: @[instance_reducible] (#12247)
This PR adds the new transparency setting `@[instance_reducible]`. We
used to check whether a declaration had `instance` reducibility by using
the `isInstance` predicate. However, this was not a robust solution
because:

- We have scoped instances, and `isInstance` returns `true` only if the
scope is active.

- We have auxiliary declarations used to construct instances manually,
such as:

```lean
    def lt_wfRel : WellFoundedRelation Nat
```
    
`isInstance` also returns `false` for this kind of declaration.

In both cases, the declaration may be (or may have been) used to
construct an instance, but `isInstance`
returns `false`. Thus, we claim it is a mistake to check the
reducibility status using `isInstance`.
`isInstance` indicates whether a declaration is available for the type
class resolution mechanism,
not its transparency status.

**We are decoupling whether a declaration is available for type class
resolution from its transparency status.**

**Remak**: We need a update stage0 to complete this feature.

---------

Co-authored-by: Sebastian Ullrich <sebasti@nullri.ch>
2026-02-01 03:03:16 +00:00

310 lines
12 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) 2020 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Sebastian Ullrich
-/
module
prelude
public import Lean.DocString.Add
meta import Lean.Parser.Command
public section
namespace Lean.Elab
/--
Ensure the environment does not contain a declaration with name `declName`.
Recall that a private declaration cannot shadow a non-private one and vice-versa, although
they internally have different names.
-/
def checkNotAlreadyDeclared {m} [Monad m] [MonadEnv m] [MonadError m] [MonadFinally m] [MonadInfoTree m] (declName : Name) : m Unit := do
let env ← getEnv
-- Even if `declName` is public, in this function we want to consider private declarations as well
let env := env.setExporting false
withEnv env do -- also set as context for any exceptions
let addInfo declName := do
pushInfoLeaf <| .ofTermInfo {
elaborator := .anonymous, lctx := {}, expectedType? := none
stx := (← getRef)
expr := (← mkConstWithLevelParams declName)
}
if env.contains declName then
addInfo declName
match privateToUserName? declName with
| none => throwError "`{.ofConstName declName true}` has already been declared"
| some declName => throwError "private declaration `{.ofConstName declName true}` has already been declared"
if isReservedName env (privateToUserName declName) || isReservedName env (mkPrivateName (← getEnv) declName) then
throwError "`{.ofConstName declName}` is a reserved name"
if env.contains (mkPrivateName env declName) then
addInfo (mkPrivateName env declName)
throwError "a private declaration `{.ofConstName declName true}` has already been declared"
match privateToUserName? declName with
| none => pure ()
| some declName =>
if env.contains declName then
addInfo declName
throwError "a non-private declaration `{.ofConstName declName true}` has already been declared"
/-- Declaration visibility modifier. That is, whether a declaration is public or private or inherits its visibility from the outer scope. -/
inductive Visibility where
| regular | «private» | «public»
deriving Inhabited
instance : ToString Visibility where
toString
| .regular => "regular"
| .private => "private"
| .public => "public"
def Visibility.isPrivate : Visibility → Bool
| .private => true
| _ => false
def Visibility.isPublic : Visibility → Bool
| .public => true
| _ => false
def Visibility.isInferredPublic (env : Environment) (v : Visibility) : Bool :=
if env.isExporting || !env.header.isModule then !v.isPrivate else v.isPublic
/-- Whether a declaration is default, partial or nonrec. -/
inductive RecKind where
| «partial» | «nonrec» | default
deriving Inhabited
/-- Codegen-relevant modifiers. -/
inductive ComputeKind where
| regular | «meta» | «noncomputable»
deriving Inhabited, BEq
/-- Flags and data added to declarations (eg docstrings, attributes, `private`, `unsafe`, `partial`, ...). -/
structure Modifiers where
/-- Input syntax, used for adjusting declaration range (unless missing) -/
stx : TSyntax ``Parser.Command.declModifiers := ⟨.missing⟩
/--
The docstring, if present, and whether it's Verso.
-/
docString? : Option (TSyntax ``Parser.Command.docComment × Bool) := none
visibility : Visibility := Visibility.regular
isProtected : Bool := false
computeKind : ComputeKind := .regular
recKind : RecKind := RecKind.default
isUnsafe : Bool := false
attrs : Array Attribute := #[]
deriving Inhabited
def Modifiers.isPrivate (m : Modifiers) : Bool := m.visibility.isPrivate
def Modifiers.isPublic (m : Modifiers) : Bool := m.visibility.isPublic
def Modifiers.isInferredPublic (env : Environment) (m : Modifiers) : Bool :=
m.visibility.isInferredPublic env
def Modifiers.isPartial : Modifiers → Bool
| { recKind := .partial, .. } => true
| _ => false
def Modifiers.isNonrec : Modifiers → Bool
| { recKind := .nonrec, .. } => true
| _ => false
def Modifiers.isMeta (m : Modifiers) : Bool :=
m.computeKind matches .meta
def Modifiers.isNoncomputable (m : Modifiers) : Bool :=
m.computeKind matches .noncomputable
/-- Adds attribute `attr` in `modifiers` -/
def Modifiers.addAttr (modifiers : Modifiers) (attr : Attribute) : Modifiers :=
{ modifiers with attrs := modifiers.attrs.push attr }
/-- Adds attribute `attr` in `modifiers`, at the beginning -/
def Modifiers.addFirstAttr (modifiers : Modifiers) (attr : Attribute) : Modifiers :=
{ modifiers with attrs := #[attr] ++ modifiers.attrs }
/-- Filters attributes using `p` -/
def Modifiers.filterAttrs (modifiers : Modifiers) (p : Attribute → Bool) : Modifiers :=
{ modifiers with attrs := modifiers.attrs.filter p }
/-- Returns `true` if `modifiers` contains an attribute satisfying `p`. -/
def Modifiers.anyAttr (modifiers : Modifiers) (p : Attribute → Bool) : Bool :=
modifiers.attrs.any p
instance : ToFormat Modifiers := ⟨fun m =>
let components : List Format :=
(match m.docString? with
| some str => [f!"/--{str}-/"]
| none => [])
++ (match m.visibility with
| .regular => []
| .private => [f!"private"]
| .public => [f!"public"])
++ (if m.isProtected then [f!"protected"] else [])
++ (match m.computeKind with | .regular => [] | .meta => [f!"meta"] | .noncomputable => [f!"noncomputable"])
++ (match m.recKind with | RecKind.partial => [f!"partial"] | RecKind.nonrec => [f!"nonrec"] | _ => [])
++ (if m.isUnsafe then [f!"unsafe"] else [])
++ m.attrs.toList.map (fun attr => format attr)
Format.bracket "{" (Format.joinSep components ("," ++ Format.line)) "}"⟩
instance : ToString Modifiers := ⟨toString ∘ format⟩
/--
Retrieve doc string from `stx` of the form `(docComment)?`.
-/
def expandOptDocComment? [Monad m] [MonadError m] (optDocComment : Syntax) : m (Option String) :=
match optDocComment.getOptional? with
| none => return none
| some s => match s[1] with
| .atom _ val => return some (String.Pos.Raw.extract val 0 (val.rawEndPos.unoffsetBy ⟨2⟩))
| _ => throwErrorAt s "unexpected doc string{indentD s[1]}"
section Methods
variable [Monad m] [MonadEnv m] [MonadResolveName m] [MonadError m] [MonadFinally m] [MonadMacroAdapter m] [MonadRecDepth m] [MonadTrace m] [MonadOptions m] [AddMessageContext m] [MonadLog m] [MonadInfoTree m] [MonadLiftT IO m]
/-- Elaborate declaration modifiers (i.e., attributes, `partial`, `private`, `protected`, `unsafe`, `meta`, `noncomputable`, doc string)-/
def elabModifiers (stx : TSyntax ``Parser.Command.declModifiers) : m Modifiers := do
let docCommentStx := stx.raw[0]
let attrsStx := stx.raw[1]
let visibilityStx := stx.raw[2]
let protectedStx := stx.raw[3]
let computeKind :=
if stx.raw[4].isNone then
.regular
else if stx.raw[4][0].getKind == ``Parser.Command.meta then
.meta
else
.noncomputable
let unsafeStx := stx.raw[5]
let recKind :=
if stx.raw[6].isNone then
RecKind.default
else if stx.raw[6][0].getKind == ``Parser.Command.partial then
RecKind.partial
else
RecKind.nonrec
let docString? := docCommentStx.getOptional?.map (TSyntax.mk ·, doc.verso.get (← getOptions))
let visibility ← match visibilityStx.getOptional? with
| none => pure .regular
| some v =>
match v with
| `(Parser.Command.visibility| private) => pure .private
| `(Parser.Command.visibility| public) => pure .public
| _ => throwErrorAt v "unexpected visibility modifier"
let isProtected := !protectedStx.isNone
let attrs ← match attrsStx.getOptional? with
| none => pure #[]
| some attrs => elabDeclAttrs attrs
return {
stx, docString?, visibility, isProtected, computeKind, recKind, attrs,
isUnsafe := !unsafeStx.isNone
}
/--
Ensure the function has not already been declared, and apply the given visibility setting to `declName`.
If `private`, return the updated name using our internal encoding for private names.
If `protected`, register `declName` as protected in the environment.
-/
def applyVisibility (modifiers : Modifiers) (declName : Name) : m Name := do
let mut declName := declName
if !modifiers.visibility.isInferredPublic (← getEnv) then
declName := mkPrivateName (← getEnv) declName
checkNotAlreadyDeclared declName
if modifiers.isProtected then
modifyEnv fun env => addProtected env declName
pure declName
def checkIfShadowingStructureField (declName : Name) : m Unit := do
match declName with
| Name.str pre .. =>
if isStructure (← getEnv) pre then
let fieldNames := getStructureFieldsFlattened (← getEnv) pre
for fieldName in fieldNames do
if pre ++ fieldName == declName then
throwError "invalid declaration name `{.ofConstName declName}`, structure `{pre}` has field `{fieldName}`"
| _ => pure ()
def mkDeclName (currNamespace : Name) (modifiers : Modifiers) (shortName : Name) : m (Name × Name) := do
let mut shortName := shortName
let mut currNamespace := currNamespace
let view := extractMacroScopes shortName
let name := view.name
let isRootName := (`_root_).isPrefixOf name
if name == `_root_ then
throwError "invalid declaration name `_root_`, `_root_` is a prefix used to refer to the 'root' namespace"
let declName := if isRootName then { view with name := name.replacePrefix `_root_ Name.anonymous }.review else currNamespace ++ shortName
if isRootName then
let .str p s := name | throwError "invalid declaration name `{name}`"
shortName := Name.mkSimple s
currNamespace := p.replacePrefix `_root_ Name.anonymous
checkIfShadowingStructureField declName
let declName ← applyVisibility modifiers declName
if modifiers.isProtected then
match currNamespace with
| .str _ s => return (declName, Name.mkSimple s ++ shortName)
| _ =>
if shortName.isAtomic then
throwError "protected declarations must be in a namespace"
return (declName, shortName)
else
return (declName, shortName)
/--
`declId` is of the form
```
leading_parser ident >> optional (".{" >> sepBy1 ident ", " >> "}")
```
but we also accept a single identifier to users to make macro writing more convenient .
-/
def expandDeclIdCore (declId : Syntax) : Name × Syntax :=
if declId.isIdent then
(declId.getId, mkNullNode)
else
let id := declId[0].getId
let optUnivDeclStx := declId[1]
(id, optUnivDeclStx)
/-- `expandDeclId` resulting type. -/
structure ExpandDeclIdResult where
/-- Short name for recursively referring to the declaration. -/
shortName : Name
/-- Fully qualified name that will be used to name the declaration in the kernel. -/
declName : Name
/-- Universe parameter names provided using the `universe` command and `.{...}` notation. -/
levelNames : List Name
/-- The docstring, and whether it's Verso -/
docString? : Option (TSyntax ``Parser.Command.docComment × Bool)
open Lean.Elab.Term (TermElabM)
/--
Given a declaration identifier (e.g., `ident (".{" ident,+ "}")?`) that may contain explicit universe parameters
- Ensure the new universe parameters do not shadow universe parameters declared using `universe` command.
- Create the fully qualified named for the declaration using the current namespace, and given `modifiers`
- Create a short version for recursively referring to the declaration. Recall that the `protected` modifier affects the generation of the short name.
The result also contains the universe parameters provided using `universe` command, and the `.{...}` notation.
This commands also stores the doc string stored in `modifiers`.
-/
def expandDeclId (currNamespace : Name) (currLevelNames : List Name) (declId : Syntax) (modifiers : Modifiers) : TermElabM ExpandDeclIdResult := do
-- ident >> optional (".{" >> sepBy1 ident ", " >> "}")
let (shortName, optUnivDeclStx) := expandDeclIdCore declId
let levelNames ← if optUnivDeclStx.isNone then
pure currLevelNames
else
let extraLevels := optUnivDeclStx[1].getArgs.getEvenElems
extraLevels.foldlM
(fun levelNames idStx =>
let id := idStx.getId
if levelNames.elem id then
withRef idStx <| throwAlreadyDeclaredUniverseLevel id
else
pure (id :: levelNames))
currLevelNames
let (declName, shortName) ← withRef declId <| mkDeclName currNamespace modifiers shortName
let docString? := modifiers.docString?
return { shortName, declName, levelNames, docString? }
end Methods
end Lean.Elab