Compare commits

...

3 Commits

Author SHA1 Message Date
Henrik Böving
9c2a1d33b3 perf: use a small dequeue in freeing objects 2026-04-01 09:20:30 +00:00
Julia Markus Himmel
48800e438c fix: assorted fixes in the string library (#13201)
This PR fixes several issues in `Init.Data.String`, most of them typos.

We also move the remaining material out of `Init.Data.String.Lemmas` to
`Init.Data.String.Lemmas.StringOrder`, which shortens the pole.
2026-03-31 06:28:20 +00:00
Wojciech Różowski
f395593ffc feat: missingDocs linter warns about empty doc strings (#13188)
This PR extends the `missingDocs` linter to detect and warn about empty
doc strings (e.g. `/---/` or `/-- -/`), in addition to missing doc
strings. Previously, an empty doc comment would silence the linter even
though it provides no documentation value. Now empty doc strings produce
a distinct "empty doc string for ..." warning, while `@[inherit_doc]`
still suppresses warnings as before.
2026-03-30 19:48:25 +00:00
18 changed files with 360 additions and 120 deletions

View File

@@ -9,7 +9,7 @@ prelude
public import Init.Data.Order.Ord
public import Init.Data.String.Basic
import Init.Data.Char.Lemmas
import Init.Data.String.Lemmas
import Init.Data.String.Lemmas.StringOrder
public section

View File

@@ -17,6 +17,7 @@ namespace Std
/--
Appends all the elements in the iterator, in order.
-/
@[inline]
public def Iter.joinString {α β : Type} [Iterator α Id β] [ToString β]
(it : Std.Iter (α := α) β) : String :=
(it.map toString).fold (init := "") (· ++ ·)

View File

@@ -20,49 +20,4 @@ public import Init.Data.String.Lemmas.Intercalate
public import Init.Data.String.Lemmas.Iter
public import Init.Data.String.Lemmas.Hashable
public import Init.Data.String.Lemmas.TakeDrop
import Init.Data.Order.Lemmas
public import Init.Data.String.Basic
import Init.Data.Char.Lemmas
import Init.Data.Char.Order
import Init.Data.List.Lex
public section
open Std
namespace String
@[deprecated toList_inj (since := "2025-10-30")]
protected theorem data_eq_of_eq {a b : String} (h : a = b) : a.toList = b.toList :=
h rfl
@[deprecated toList_inj (since := "2025-10-30")]
protected theorem ne_of_data_ne {a b : String} (h : a.toList b.toList) : a b := by
simpa [ toList_inj]
@[simp] protected theorem not_le {a b : String} : ¬ a b b < a := Decidable.not_not
@[simp] protected theorem not_lt {a b : String} : ¬ a < b b a := Iff.rfl
@[simp] protected theorem le_refl (a : String) : a a := List.le_refl _
@[simp] protected theorem lt_irrefl (a : String) : ¬ a < a := List.lt_irrefl _
attribute [local instance] Char.notLTTrans Char.ltTrichotomous Char.ltAsymm
protected theorem le_trans {a b c : String} : a b b c a c := List.le_trans
protected theorem lt_trans {a b c : String} : a < b b < c a < c := List.lt_trans
protected theorem le_total (a b : String) : a b b a := List.le_total _ _
protected theorem le_antisymm {a b : String} : a b b a a = b := fun h₁ h₂ => String.ext (List.le_antisymm (as := a.toList) (bs := b.toList) h₁ h₂)
protected theorem lt_asymm {a b : String} (h : a < b) : ¬ b < a := List.lt_asymm h
protected theorem ne_of_lt {a b : String} (h : a < b) : a b := by
have := String.lt_irrefl a
intro h; subst h; contradiction
instance instIsLinearOrder : IsLinearOrder String := by
apply IsLinearOrder.of_le
case le_antisymm => constructor; apply String.le_antisymm
case le_trans => constructor; apply String.le_trans
case le_total => constructor; apply String.le_total
instance : LawfulOrderLT String where
lt_iff a b := by
simp [ String.not_le, Decidable.imp_iff_not_or, Std.Total.total]
end String
public import Init.Data.String.Lemmas.StringOrder

View File

@@ -40,7 +40,7 @@ framework.
/--
This data-carrying typeclass is used to give semantics to a pattern type that implements
{name}`ForwardPattern` and/or {name}`ToForwardSearcher` by providing an abstract, not necessarily
decidable {name}`PatternModel.Matches` predicate that implementates of {name}`ForwardPattern`
decidable {name}`PatternModel.Matches` predicate that implementations of {name}`ForwardPattern`
and {name}`ToForwardSearcher` can be validated against.
Correctness results for generic functions relying on the pattern infrastructure, for example the
@@ -151,7 +151,7 @@ theorem IsLongestMatch.le_of_isMatch {pat : ρ} [PatternModel pat] {s : Slice} {
/--
Predicate stating that the region between the start of the slice {name}`s` and the position
{name}`pos` matches the patten {name}`pat`, and that there is no longer match starting at the
{name}`pos` matches the pattern {name}`pat`, and that there is no longer match starting at the
beginning of the slice. This is what a correct matcher should match.
In some cases, being a match and being a longest match will coincide, see
@@ -228,7 +228,7 @@ theorem isLongestRevMatch_iff_isRevMatch {ρ : Type} (pat : ρ) [PatternModel pa
exact ht₅ (NoSuffixPatternModel.eq_empty _ _ ht₂ (ht₅'' ht₂'))
/--
Predicate stating that the slice formed by {name}`startPos` and {name}`endPos` contains is a match
Predicate stating that the slice formed by {name}`startPos` and {name}`endPos` contains a match
of {name}`pat` in {name}`s` and it is longest among matches starting at {name}`startPos`.
-/
structure IsLongestMatchAt (pat : ρ) [PatternModel pat] {s : Slice} (startPos endPos : s.Pos) : Prop where
@@ -411,7 +411,7 @@ theorem not_revMatchesAt_startPos {pat : ρ} [PatternModel pat] {s : Slice} :
intro h
simpa [ Pos.ofSliceTo_inj] using h.ne_endPos
theorem revMatchesAt_iff_revMatchesAt_ofSliceto {pat : ρ} [PatternModel pat] {s : Slice} {base : s.Pos}
theorem revMatchesAt_iff_revMatchesAt_ofSliceTo {pat : ρ} [PatternModel pat] {s : Slice} {base : s.Pos}
{pos : (s.sliceTo base).Pos} : RevMatchesAt pat pos RevMatchesAt pat (Pos.ofSliceTo pos) := by
simp only [revMatchesAt_iff_exists_isLongestRevMatchAt]
constructor
@@ -505,8 +505,8 @@ theorem LawfulForwardPatternModel.skipPrefix?_eq_none_iff {ρ : Type} {pat : ρ}
/--
Predicate stating compatibility between {name}`PatternModel` and {name}`BackwardPattern`.
This extends {name}`LawfulForwardPattern`, but it is much stronger because it forces the
{name}`ForwardPattern` to match the longest prefix of the given slice that matches the property
This extends {name}`LawfulBackwardPattern`, but it is much stronger because it forces the
{name}`BackwardPattern` to match the longest prefix of the given slice that matches the property
supplied by the {name}`PatternModel` instance.
-/
class LawfulBackwardPatternModel {ρ : Type} (pat : ρ) [BackwardPattern pat]

View File

@@ -65,7 +65,7 @@ theorem startsWith_prop_eq_head? {P : Char → Prop} [DecidablePred P] {s : Slic
s.startsWith P = s.copy.toList.head?.any (decide <| P ·) := by
simp [startsWith_prop_eq_startsWith_decide, startsWith_bool_eq_head?]
theorem eq_append_of_dropPrefix_prop_eq_some {P : Char Prop} [DecidablePred P] {s res : Slice} (h : s.dropPrefix? P = some res) :
theorem eq_append_of_dropPrefix?_prop_eq_some {P : Char Prop} [DecidablePred P] {s res : Slice} (h : s.dropPrefix? P = some res) :
c, s.copy = singleton c ++ res.copy P c := by
rw [dropPrefix?_prop_eq_dropPrefix?_decide] at h
simpa using eq_append_of_dropPrefix?_bool_eq_some h
@@ -162,7 +162,7 @@ theorem startsWith_prop_eq_head? {P : Char → Prop} [DecidablePred P] {s : Stri
theorem eq_append_of_dropPrefix?_prop_eq_some {P : Char Prop} [DecidablePred P] {s : String} {res : Slice}
(h : s.dropPrefix? P = some res) : c, s = singleton c ++ res.copy P c := by
rw [dropPrefix?_eq_dropPrefix?_toSlice] at h
simpa using Slice.eq_append_of_dropPrefix_prop_eq_some h
simpa using Slice.eq_append_of_dropPrefix?_prop_eq_some h
theorem skipSuffix?_bool_eq_some_iff {p : Char Bool} {s : String} {pos : s.Pos} :
s.skipSuffix? p = some pos h, pos = s.endPos.prev h p ((s.endPos.prev h).get (by simp)) = true := by

View File

@@ -0,0 +1,49 @@
/-
Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
module
prelude
public import Init.Data.String.Basic
public import Init.Data.Order.Classes
import Init.Data.List.Lex
import Init.Data.Char.Lemmas
import Init.Data.Char.Order
import Init.Data.Order.Factories
import Init.Data.Order.Lemmas
public section
open Std
namespace String
@[simp] protected theorem not_le {a b : String} : ¬ a b b < a := Decidable.not_not
@[simp] protected theorem not_lt {a b : String} : ¬ a < b b a := Iff.rfl
@[simp] protected theorem le_refl (a : String) : a a := List.le_refl _
@[simp] protected theorem lt_irrefl (a : String) : ¬ a < a := List.lt_irrefl _
attribute [local instance] Char.notLTTrans Char.ltTrichotomous Char.ltAsymm
protected theorem le_trans {a b c : String} : a b b c a c := List.le_trans
protected theorem lt_trans {a b c : String} : a < b b < c a < c := List.lt_trans
protected theorem le_total (a b : String) : a b b a := List.le_total _ _
protected theorem le_antisymm {a b : String} : a b b a a = b := fun h₁ h₂ => String.ext (List.le_antisymm (as := a.toList) (bs := b.toList) h₁ h₂)
protected theorem lt_asymm {a b : String} (h : a < b) : ¬ b < a := List.lt_asymm h
protected theorem ne_of_lt {a b : String} (h : a < b) : a b := by
have := String.lt_irrefl a
intro h; subst h; contradiction
instance instIsLinearOrder : IsLinearOrder String := by
apply IsLinearOrder.of_le
case le_antisymm => constructor; apply String.le_antisymm
case le_trans => constructor; apply String.le_trans
case le_total => constructor; apply String.le_total
instance : LawfulOrderLT String where
lt_iff a b := by
simp [ String.not_le, Decidable.imp_iff_not_or, Std.Total.total]
end String

View File

@@ -706,14 +706,14 @@ Returns {name}`none` otherwise.
This function is generic over all currently supported patterns.
-/
@[inline]
def Pos.revSkip? {s : Slice} (pos : s.Pos) (pat : ρ) [ForwardPattern pat] : Option s.Pos :=
((s.sliceFrom pos).skipPrefix? pat).map Pos.ofSliceFrom
def Pos.revSkip? {s : Slice} (pos : s.Pos) (pat : ρ) [BackwardPattern pat] : Option s.Pos :=
((s.sliceFrom pos).skipSuffix? pat).map Pos.ofSliceFrom
/--
If {name}`pat` matches a suffix of {name}`s`, returns the remainder. Returns {name}`none` otherwise.
Use {name (scope := "Init.Data.String.Slice")}`String.Slice.dropSuffix` to return the slice
unchanged when {name}`pat` does not match a prefix.
unchanged when {name}`pat` does not match a suffix.
This function is generic over all currently supported patterns.
@@ -775,7 +775,7 @@ def Pos.revSkipWhile {s : Slice} (pos : s.Pos) (pat : ρ) [BackwardPattern pat]
termination_by pos.down
/--
Returns the position a the start of the longest suffix of {name}`s` for which {name}`pat` matches
Returns the position at the start of the longest suffix of {name}`s` for which {name}`pat` matches
(potentially repeatedly).
-/
@[inline]

View File

@@ -314,7 +314,7 @@ Returns {name}`none` otherwise.
This function is generic over all currently supported patterns.
-/
@[inline]
def Pos.revSkip? {s : String} (pos : s.Pos) (pat : ρ) [ForwardPattern pat] : Option s.Pos :=
def Pos.revSkip? {s : String} (pos : s.Pos) (pat : ρ) [BackwardPattern pat] : Option s.Pos :=
(pos.toSlice.revSkip? pat).map Pos.ofToSlice
/--
@@ -461,7 +461,7 @@ def dropPrefix? (s : String) (pat : ρ) [ForwardPattern pat] : Option String.Sli
If {name}`pat` matches a suffix of {name}`s`, returns the remainder. Returns {name}`none` otherwise.
Use {name (scope := "Init.Data.String.TakeDrop")}`String.dropSuffix` to return the slice
unchanged when {name}`pat` does not match a prefix.
unchanged when {name}`pat` does not match a suffix.
This is a cheap operation because it does not allocate a new string to hold the result.
To convert the result into a string, use {name}`String.Slice.copy`.

View File

@@ -36,9 +36,6 @@ private local instance : ToString Int where
private local instance : Repr Int where
reprPrec i prec := if i < 0 then Repr.addAppParen (toString i) prec else toString i
private local instance : Append String where
append := String.Internal.append
/-- Internal representation of a linear combination of atoms, and a constant term. -/
structure LinearCombo where
/-- Constant term. -/

View File

@@ -60,7 +60,7 @@ instance : EmptyCollection (Trie α) :=
instance : Inhabited (Trie α) where
default := empty
/-- Insert or update the value at a the given key `s`. -/
/-- Insert or update the value at the given key `s`. -/
partial def upsert (t : Trie α) (s : String) (f : Option α α) : Trie α :=
let rec insertEmpty (i : Nat) : Trie α :=
if h : i < s.utf8ByteSize then
@@ -100,7 +100,7 @@ partial def upsert (t : Trie α) (s : String) (f : Option αα) : Trie α :
node (f v) cs ts
loop 0 t
/-- Inserts a value at a the given key `s`, overriding an existing value if present. -/
/-- Inserts a value at the given key `s`, overriding an existing value if present. -/
partial def insert (t : Trie α) (s : String) (val : α) : Trie α :=
upsert t s (fun _ => val)

View File

@@ -85,6 +85,10 @@ structure State where
-/
lctx : LocalContext
/--
The local instances.
The `MonadLift TermElabM DocM` instance runs the lifted action with these instances, so elaboration
commands that mutate this state cause it to take effect in subsequent commands.
-/
localInstances : LocalInstances
/--

View File

@@ -112,15 +112,37 @@ builtin_initialize
def lint (stx : Syntax) (msg : String) : CommandElabM Unit :=
logLint linter.missingDocs stx m!"missing doc string for {msg}"
def lintEmpty (stx : Syntax) (msg : String) : CommandElabM Unit :=
logLint linter.missingDocs stx m!"empty doc string for {msg}"
def lintNamed (stx : Syntax) (msg : String) : CommandElabM Unit :=
lint stx s!"{msg} {stx.getId}"
def lintEmptyNamed (stx : Syntax) (msg : String) : CommandElabM Unit :=
lintEmpty stx s!"{msg} {stx.getId}"
def lintField (parent stx : Syntax) (msg : String) : CommandElabM Unit :=
lint stx s!"{msg} {parent.getId}.{stx.getId}"
def lintEmptyField (parent stx : Syntax) (msg : String) : CommandElabM Unit :=
lintEmpty stx s!"{msg} {parent.getId}.{stx.getId}"
def lintStructField (parent stx : Syntax) (msg : String) : CommandElabM Unit :=
lint stx s!"{msg} {parent.getId}.{stx.getId}"
private def isEmptyDocString (docOpt : Syntax) : CommandElabM Bool := do
if docOpt.isNone then return false
let docStx : TSyntax `Lean.Parser.Command.docComment := docOpt[0]
-- Verso doc comments with interpolated content cannot be extracted as plain text,
-- but they are clearly not empty.
if let .node _ `Lean.Parser.Command.versoCommentBody _ := docStx.raw[1] then
if !docStx.raw[1][0].isAtom then return false
let text getDocStringText docStx
return text.trimAscii.isEmpty
def isMissingDoc (docOpt : Syntax) : CommandElabM Bool := do
return docOpt.isNone || ( isEmptyDocString docOpt)
def hasInheritDoc (attrs : Syntax) : Bool :=
attrs[0][1].getSepArgs.any fun attr =>
attr[1].isOfKind ``Parser.Attr.simple &&
@@ -130,38 +152,68 @@ def hasTacticAlt (attrs : Syntax) : Bool :=
attrs[0][1].getSepArgs.any fun attr =>
attr[1].isOfKind ``Parser.Attr.tactic_alt
def declModifiersPubNoDoc (mods : Syntax) : CommandElabM Bool := do
def declModifiersDocStatus (mods : Syntax) : CommandElabM (Option Bool) := do
let isPublic := if ( getEnv).header.isModule && !( getScope).isPublic then
mods[2][0].getKind == ``Command.public else
mods[2][0].getKind != ``Command.private
return isPublic && mods[0].isNone && !hasInheritDoc mods[1]
if !isPublic || hasInheritDoc mods[1] then return none
if mods[0].isNone then return some false
if ( isEmptyDocString mods[0]) then return some true
return none
def lintDeclHead (k : SyntaxNodeKind) (id : Syntax) : CommandElabM Unit := do
if k == ``«abbrev» then lintNamed id "public abbrev"
else if k == ``definition then lintNamed id "public def"
else if k == ``«opaque» then lintNamed id "public opaque"
else if k == ``«axiom» then lintNamed id "public axiom"
else if k == ``«inductive» then lintNamed id "public inductive"
else if k == ``classInductive then lintNamed id "public inductive"
else if k == ``«structure» then lintNamed id "public structure"
def declModifiersPubNoDoc (mods : Syntax) : CommandElabM Bool := do
return ( declModifiersDocStatus mods).isSome
private def lintDocStatus (isEmpty : Bool) (stx : Syntax) (msg : String) : CommandElabM Unit :=
if isEmpty then lintEmpty stx msg else lint stx msg
private def lintDocStatusNamed (isEmpty : Bool) (stx : Syntax) (msg : String) : CommandElabM Unit :=
if isEmpty then lintEmptyNamed stx msg else lintNamed stx msg
private def lintDocStatusField (isEmpty : Bool) (parent stx : Syntax) (msg : String) :
CommandElabM Unit :=
if isEmpty then lintEmptyField parent stx msg else lintField parent stx msg
def lintDeclHead (k : SyntaxNodeKind) (id : Syntax) (isEmpty : Bool := false) :
CommandElabM Unit := do
if k == ``«abbrev» then lintDocStatusNamed isEmpty id "public abbrev"
else if k == ``definition then lintDocStatusNamed isEmpty id "public def"
else if k == ``«opaque» then lintDocStatusNamed isEmpty id "public opaque"
else if k == ``«axiom» then lintDocStatusNamed isEmpty id "public axiom"
else if k == ``«inductive» then lintDocStatusNamed isEmpty id "public inductive"
else if k == ``classInductive then lintDocStatusNamed isEmpty id "public inductive"
else if k == ``«structure» then lintDocStatusNamed isEmpty id "public structure"
private def docOptStatus (docOpt attrs : Syntax) (checkTacticAlt := false) :
CommandElabM (Option Bool) := do
if hasInheritDoc attrs then return none
if checkTacticAlt && hasTacticAlt attrs then return none
if docOpt.isNone then return some false
if ( isEmptyDocString docOpt) then return some true
return none
@[builtin_missing_docs_handler declaration]
def checkDecl : SimpleHandler := fun stx => do
let head := stx[0]; let rest := stx[1]
if head[2][0].getKind == ``Command.private then return -- not private
let k := rest.getKind
if ( declModifiersPubNoDoc head) then -- no doc string
lintDeclHead k rest[1][0]
if let some isEmpty declModifiersDocStatus head then
lintDeclHead k rest[1][0] isEmpty
if k == ``«inductive» || k == ``classInductive then
for stx in rest[4].getArgs do
let head := stx[2]
if stx[0].isNone && ( declModifiersPubNoDoc head) then
lintField rest[1][0] stx[3] "public constructor"
-- Constructor has two doc comment positions: the leading one before `|` (stx[0])
-- and the one inside declModifiers (head[0]). If either is non-empty, skip.
let leadingEmpty isEmptyDocString stx[0]
if !stx[0].isNone && !leadingEmpty then
pure () -- constructor has a non-empty leading doc comment
else if let some modsEmpty declModifiersDocStatus head then
lintDocStatusField (leadingEmpty || modsEmpty) rest[1][0] stx[3] "public constructor"
unless rest[5].isNone do
for stx in rest[5][0][1].getArgs do
let head := stx[0]
if ( declModifiersPubNoDoc head) then -- no doc string
lintField rest[1][0] stx[1] "computed field"
if let some isEmpty declModifiersDocStatus head then
lintDocStatusField isEmpty rest[1][0] stx[1] "computed field"
else if rest.getKind == ``«structure» then
unless rest[4][2].isNone do
let redecls : Std.HashSet String.Pos.Raw :=
@@ -173,45 +225,52 @@ def checkDecl : SimpleHandler := fun stx => do
else s
else s
let parent := rest[1][0]
let lint1 stx := do
let lint1 isEmpty stx := do
if let some range := stx.getRange? then
if redecls.contains range.start then return
lintField parent stx "public field"
lintDocStatusField isEmpty parent stx "public field"
for stx in rest[4][2][0].getArgs do
let head := stx[0]
if ( declModifiersPubNoDoc head) then
if let some isEmpty declModifiersDocStatus head then
if stx.getKind == ``structSimpleBinder then
lint1 stx[1]
lint1 isEmpty stx[1]
else
for stx in stx[2].getArgs do
lint1 stx
lint1 isEmpty stx
@[builtin_missing_docs_handler «initialize»]
def checkInit : SimpleHandler := fun stx => do
if !stx[2].isNone && ( declModifiersPubNoDoc stx[0]) then
lintNamed stx[2][0] "initializer"
if !stx[2].isNone then
if let some isEmpty declModifiersDocStatus stx[0] then
lintDocStatusNamed isEmpty stx[2][0] "initializer"
@[builtin_missing_docs_handler «notation»]
def checkNotation : SimpleHandler := fun stx => do
if stx[0].isNone && stx[2][0][0].getKind != ``«local» && !hasInheritDoc stx[1] then
if stx[5].isNone then lint stx[3] "notation"
else lintNamed stx[5][0][3] "notation"
if stx[2][0][0].getKind != ``«local» then
if let some isEmpty docOptStatus stx[0] stx[1] then
if stx[5].isNone then lintDocStatus isEmpty stx[3] "notation"
else lintDocStatusNamed isEmpty stx[5][0][3] "notation"
@[builtin_missing_docs_handler «mixfix»]
def checkMixfix : SimpleHandler := fun stx => do
if stx[0].isNone && stx[2][0][0].getKind != ``«local» && !hasInheritDoc stx[1] then
if stx[5].isNone then lint stx[3] stx[3][0].getAtomVal
else lintNamed stx[5][0][3] stx[3][0].getAtomVal
if stx[2][0][0].getKind != ``«local» then
if let some isEmpty docOptStatus stx[0] stx[1] then
if stx[5].isNone then lintDocStatus isEmpty stx[3] stx[3][0].getAtomVal
else lintDocStatusNamed isEmpty stx[5][0][3] stx[3][0].getAtomVal
@[builtin_missing_docs_handler «syntax»]
def checkSyntax : SimpleHandler := fun stx => do
if stx[0].isNone && stx[2][0][0].getKind != ``«local» && !hasInheritDoc stx[1] && !hasTacticAlt stx[1] then
if stx[5].isNone then lint stx[3] "syntax"
else lintNamed stx[5][0][3] "syntax"
if stx[2][0][0].getKind != ``«local» then
if let some isEmpty docOptStatus stx[0] stx[1] (checkTacticAlt := true) then
if stx[5].isNone then lintDocStatus isEmpty stx[3] "syntax"
else lintDocStatusNamed isEmpty stx[5][0][3] "syntax"
def mkSimpleHandler (name : String) (declNameStxIdx := 2) : SimpleHandler := fun stx => do
if stx[0].isNone then
lintNamed stx[declNameStxIdx] name
if ( isMissingDoc stx[0]) then
if ( isEmptyDocString stx[0]) then
lintEmptyNamed stx[declNameStxIdx] name
else
lintNamed stx[declNameStxIdx] name
@[builtin_missing_docs_handler syntaxAbbrev]
def checkSyntaxAbbrev : SimpleHandler := mkSimpleHandler "syntax"
@@ -221,20 +280,22 @@ def checkSyntaxCat : SimpleHandler := mkSimpleHandler "syntax category"
@[builtin_missing_docs_handler «macro»]
def checkMacro : SimpleHandler := fun stx => do
if stx[0].isNone && stx[2][0][0].getKind != ``«local» && !hasInheritDoc stx[1] && !hasTacticAlt stx[1] then
if stx[5].isNone then lint stx[3] "macro"
else lintNamed stx[5][0][3] "macro"
if stx[2][0][0].getKind != ``«local» then
if let some isEmpty docOptStatus stx[0] stx[1] (checkTacticAlt := true) then
if stx[5].isNone then lintDocStatus isEmpty stx[3] "macro"
else lintDocStatusNamed isEmpty stx[5][0][3] "macro"
@[builtin_missing_docs_handler «elab»]
def checkElab : SimpleHandler := fun stx => do
if stx[0].isNone && stx[2][0][0].getKind != ``«local» && !hasInheritDoc stx[1] && !hasTacticAlt stx[1] then
if stx[5].isNone then lint stx[3] "elab"
else lintNamed stx[5][0][3] "elab"
if stx[2][0][0].getKind != ``«local» then
if let some isEmpty docOptStatus stx[0] stx[1] (checkTacticAlt := true) then
if stx[5].isNone then lintDocStatus isEmpty stx[3] "elab"
else lintDocStatusNamed isEmpty stx[5][0][3] "elab"
@[builtin_missing_docs_handler classAbbrev]
def checkClassAbbrev : SimpleHandler := fun stx => do
if ( declModifiersPubNoDoc stx[0]) then
lintNamed stx[3] "class abbrev"
if let some isEmpty declModifiersDocStatus stx[0] then
lintDocStatusNamed isEmpty stx[3] "class abbrev"
@[builtin_missing_docs_handler Parser.Tactic.declareSimpLikeTactic]
def checkSimpLike : SimpleHandler := mkSimpleHandler "simp-like tactic"
@@ -244,8 +305,8 @@ def checkRegisterBuiltinOption : SimpleHandler := mkSimpleHandler (declNameStxId
@[builtin_missing_docs_handler Option.registerOption]
def checkRegisterOption : SimpleHandler := fun stx => do
if ( declModifiersPubNoDoc stx[0]) then
lintNamed stx[2] "option"
if let some isEmpty declModifiersDocStatus stx[0] then
lintDocStatusNamed isEmpty stx[2] "option"
@[builtin_missing_docs_handler registerSimpAttr]
def checkRegisterSimpAttr : SimpleHandler := mkSimpleHandler "simp attr"

View File

@@ -183,7 +183,8 @@ public theorem toInt?_repr (a : Int) : a.repr.toInt? = some a := by
rw [repr_eq_if]
split <;> (simp; omega)
public theorem isInt?_repr (a : Int) : a.repr.isInt = true := by
@[simp]
public theorem isInt_repr (a : Int) : a.repr.isInt = true := by
simp [ String.isSome_toInt?]
public theorem repr_injective {a b : Int} (h : Int.repr a = Int.repr b) : a = b := by

View File

@@ -239,7 +239,7 @@ def ofFin' {lo : Nat} (fin : Fin (Nat.succ hi)) (h : lo ≤ hi) : Bounded.LE lo
else ofNat' lo (And.intro (Nat.le_refl lo) h)
/--
Creates a new `Bounded.LE` using a the modulus of a number.
Creates a new `Bounded.LE` using the modulus of a number.
-/
@[inline]
def byEmod (b : Int) (i : Int) (hi : i > 0) : Bounded.LE 0 (i - 1) := by
@@ -252,7 +252,7 @@ def byEmod (b : Int) (i : Int) (hi : i > 0) : Bounded.LE 0 (i - 1) := by
exact Int.emod_lt_of_pos b hi
/--
Creates a new `Bounded.LE` using a the Truncating modulus of a number.
Creates a new `Bounded.LE` using the Truncating modulus of a number.
-/
@[inline]
def byMod (b : Int) (i : Int) (hi : 0 < i) : Bounded.LE (- (i - 1)) (i - 1) := by

View File

@@ -415,17 +415,128 @@ static void lean_del_core(object * o, object * & todo) {
}
}
// Adaptive deque for lean_dec_ref_cold.
// Uses a small stack-allocated circular buffer processed in FIFO (BFS) order
// for better cache locality when freeing wide structures (arrays, multi-field
// constructors). When the buffer is full, excess objects overflow to the
// existing in-object linked list (DFS order).
#define LEAN_DEC_DEQUE_CAP 32u
#define LEAN_DEC_DEQUE_MASK (LEAN_DEC_DEQUE_CAP - 1u)
struct lean_del_deque {
lean_object * buf[LEAN_DEC_DEQUE_CAP];
unsigned head;
unsigned tail;
lean_object * overflow;
};
static inline void deque_enqueue(lean_del_deque & dq, lean_object * o) {
unsigned next_tail = (dq.tail + 1) & LEAN_DEC_DEQUE_MASK;
if (LEAN_LIKELY(next_tail != dq.head)) {
dq.buf[dq.tail] = o;
dq.tail = next_tail;
} else {
push_back(dq.overflow, o);
}
}
static inline lean_object * deque_pop(lean_del_deque & dq) {
if (LEAN_LIKELY(dq.head != dq.tail)) {
lean_object * r = dq.buf[dq.head];
dq.head = (dq.head + 1) & LEAN_DEC_DEQUE_MASK;
return r;
}
return pop_back(dq.overflow);
}
static inline bool deque_empty(lean_del_deque const & dq) {
return dq.head == dq.tail && dq.overflow == nullptr;
}
static inline void dec_deque(lean_object * o, lean_del_deque & dq) {
if (lean_is_scalar(o))
return;
if (LEAN_LIKELY(o->m_rc > 1)) {
o->m_rc--;
} else if (o->m_rc == 1) {
deque_enqueue(dq, o);
} else if (o->m_rc == 0) {
return;
} else if (std::atomic_fetch_add_explicit(lean_get_rc_mt_addr(o), 1, std::memory_order_acq_rel) == -1) {
deque_enqueue(dq, o);
}
}
static void lean_del_core_deque(object * o, lean_del_deque & dq) {
uint8 tag = lean_ptr_tag(o);
if (tag <= LeanMaxCtorTag) {
object ** it = lean_ctor_obj_cptr(o);
object ** end = it + lean_ctor_num_objs(o);
for (; it != end; ++it) dec_deque(*it, dq);
lean_free_small_object(o);
} else {
switch (tag) {
case LeanClosure: {
object ** it = lean_closure_arg_cptr(o);
object ** end = it + lean_closure_num_fixed(o);
for (; it != end; ++it) dec_deque(*it, dq);
lean_dealloc(o, lean_closure_byte_size(o));
break;
}
case LeanArray: {
object ** it = lean_array_cptr(o);
object ** end = it + lean_array_size(o);
for (; it != end; ++it) dec_deque(*it, dq);
lean_dealloc(o, lean_array_byte_size(o));
break;
}
case LeanScalarArray:
lean_dealloc(o, lean_sarray_byte_size(o));
break;
case LeanString:
lean_dealloc(o, lean_string_byte_size(o));
break;
case LeanMPZ:
to_mpz(o)->m_value.~mpz();
lean_free_small_object(o);
break;
case LeanThunk:
if (object * c = lean_to_thunk(o)->m_closure) dec_deque(c, dq);
if (object * v = lean_to_thunk(o)->m_value) dec_deque(v, dq);
lean_free_small_object(o);
break;
case LeanRef:
if (object * v = lean_to_ref(o)->m_value) dec_deque(v, dq);
lean_free_small_object(o);
break;
case LeanTask:
deactivate_task(lean_to_task(o));
break;
case LeanPromise:
deactivate_promise(lean_to_promise(o));
break;
case LeanExternal:
lean_to_external(o)->m_class->m_finalize(lean_to_external(o)->m_data);
lean_free_small_object(o);
break;
default:
lean_unreachable();
}
}
}
extern "C" LEAN_EXPORT void lean_dec_ref_cold(lean_object * o) {
if (o->m_rc == 1 || std::atomic_fetch_add_explicit(lean_get_rc_mt_addr(o), 1, std::memory_order_acq_rel) == -1) {
#ifdef LEAN_LAZY_RC
push_back(g_to_free, o);
#else
object * todo = nullptr;
while (true) {
lean_del_core(o, todo);
if (todo == nullptr)
return;
o = pop_back(todo);
lean_del_deque dq;
dq.head = 0;
dq.tail = 0;
dq.overflow = nullptr;
deque_enqueue(dq, o);
while (!deque_empty(dq)) {
lean_del_core_deque(deque_pop(dq), dq);
}
#endif
}

View File

@@ -27,7 +27,7 @@ Note: This linter can be disabled with `set_option linter.deprecatedCoercions fa
#guard_msgs in
def h (foo : X) : Y := foo
/-- -/
/-- A docstring to make `missingDocs` linter happy-/
notation a " +' " b => a + b
@[deprecated "" (since := "")]

View File

@@ -105,3 +105,37 @@ def handleMyCmd : SimpleHandler := fun
my_command y
my_command z
-- Test: empty doc strings should be treated as missing
/---/
def emptyDoc1 (x : Nat) := x
/--
-/
def emptyDoc2 (x : Nat) := x
/-- -/
def emptyDoc3 (x : Nat) := x
-- Test: empty doc strings on other declaration kinds
/---/
inductive EmptyInd where
/---/ | emptyCtorDoc
| noCtorDoc
/---/
notation:20 "empty_nota" x y => Nat.add x y
/---/
macro "empty_macro" : term => `(my_elab)
/---/
elab "empty_elab" : term => return Lean.mkConst ``false
-- Test: @[inherit_doc] suppresses even with empty doc
@[inherit_doc hasDoc]
def inheritedDoc (x : Nat) := x
-- Test: Verso doc comments with interpolated content are not empty
/-- See {name}`hasDoc` for details. -/
def versoDoc (x : Nat) := x

View File

@@ -109,3 +109,30 @@ Note: This linter can be disabled with `set_option linter.missingDocs false`
linterMissingDocs.lean:107:11-107:12: warning: missing doc string for my_command z
Note: This linter can be disabled with `set_option linter.missingDocs false`
linterMissingDocs.lean:111:4-111:13: warning: empty doc string for public def emptyDoc1
Note: This linter can be disabled with `set_option linter.missingDocs false`
linterMissingDocs.lean:115:4-115:13: warning: empty doc string for public def emptyDoc2
Note: This linter can be disabled with `set_option linter.missingDocs false`
linterMissingDocs.lean:118:4-118:13: warning: empty doc string for public def emptyDoc3
Note: This linter can be disabled with `set_option linter.missingDocs false`
linterMissingDocs.lean:122:10-122:18: warning: empty doc string for public inductive EmptyInd
Note: This linter can be disabled with `set_option linter.missingDocs false`
linterMissingDocs.lean:123:10-123:22: warning: empty doc string for public constructor EmptyInd.emptyCtorDoc
Note: This linter can be disabled with `set_option linter.missingDocs false`
linterMissingDocs.lean:124:4-124:13: warning: missing doc string for public constructor EmptyInd.noCtorDoc
Note: This linter can be disabled with `set_option linter.missingDocs false`
linterMissingDocs.lean:127:0-127:8: warning: empty doc string for notation
Note: This linter can be disabled with `set_option linter.missingDocs false`
linterMissingDocs.lean:130:0-130:5: warning: empty doc string for macro
Note: This linter can be disabled with `set_option linter.missingDocs false`
linterMissingDocs.lean:133:0-133:4: warning: empty doc string for elab
Note: This linter can be disabled with `set_option linter.missingDocs false`