Skip to content

Commit 377f149

Browse files
authored
refactor: use String.ofList and String.toList for String <-> List Char conversion (#11017)
This PR establishes `String.ofList` and `String.toList` as the preferred method for converting between strings and lists of characters and deprecates the alternatives `String.mk`, `List.asString` and `String.data`.
1 parent c41cb64 commit 377f149

File tree

15 files changed

+227
-132
lines changed

15 files changed

+227
-132
lines changed

src/Init/Data/BitVec/Basic.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ If `n` is `0`, then one digit is returned. Otherwise, `⌊(n + 3) / 4⌋` digits
203203
-- `Internal` string functions by moving this definition out to a separate file that can live
204204
-- downstream of `Init.Data.String.Basic`.
205205
protected def toHex {n : Nat} (x : BitVec n) : String :=
206-
let s := (Nat.toDigits 16 x.toNat).asString
207-
let t := (List.replicate ((n+3) / 4 - String.Internal.length s) '0').asString
206+
let s := String.ofList (Nat.toDigits 16 x.toNat)
207+
let t := String.ofList (List.replicate ((n+3) / 4 - String.Internal.length s) '0')
208208
String.Internal.append t s
209209

210210
/-- `BitVec` representation. -/

src/Init/Data/Repr.lean

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ Examples:
226226
-/
227227
@[extern "lean_string_of_usize"]
228228
protected def _root_.USize.repr (n : @& USize) : String :=
229-
(toDigits 10 n.toNat).asString
229+
String.ofList (toDigits 10 n.toNat)
230230

231231
/-- We statically allocate and memoize reprs for small natural numbers. -/
232232
private def reprArray : Array String := Id.run do
@@ -235,14 +235,14 @@ private def reprArray : Array String := Id.run do
235235
def reprFast (n : Nat) : String :=
236236
if h : n < Nat.reprArray.size then Nat.reprArray.getInternal n h else
237237
if h : n < USize.size then (USize.ofNatLT n h).repr
238-
else (toDigits 10 n).asString
238+
else String.ofList (toDigits 10 n)
239239

240240
/--
241241
Converts a natural number to its decimal string representation.
242242
-/
243243
@[implemented_by reprFast]
244244
protected def repr (n : Nat) : String :=
245-
(toDigits 10 n).asString
245+
String.ofList (toDigits 10 n)
246246

247247
/--
248248
Converts a natural number less than `10` to the corresponding Unicode superscript digit character.
@@ -293,7 +293,7 @@ Examples:
293293
* `Nat.toSuperscriptString 35 = "³⁵"`
294294
-/
295295
def toSuperscriptString (n : Nat) : String :=
296-
(toSuperDigits n).asString
296+
String.ofList (toSuperDigits n)
297297

298298
/--
299299
Converts a natural number less than `10` to the corresponding Unicode subscript digit character.
@@ -344,7 +344,7 @@ Examples:
344344
* `Nat.toSubscriptString 35 = "₃₅"`
345345
-/
346346
def toSubscriptString (n : Nat) : String :=
347-
(toSubDigits n).asString
347+
String.ofList (toSubDigits n)
348348

349349
end Nat
350350

src/Init/Data/String/Basic.lean

Lines changed: 175 additions & 92 deletions
Large diffs are not rendered by default.

src/Init/Data/String/Bootstrap.lean

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ Examples:
138138
def String.ofList (data : List Char) : String :=
139139
⟨List.utf8Encode data,.intro data rfl⟩
140140

141-
@[extern "lean_string_mk", expose]
141+
@[extern "lean_string_mk", expose, deprecated String.ofList (since := "2025-10-30")]
142142
def String.mk (data : List Char) : String :=
143143
⟨List.utf8Encode data,.intro data rfl⟩
144144

@@ -150,9 +150,9 @@ Examples:
150150
* `[].asString = ""`
151151
* `['a', 'a', 'a'].asString = "aaa"`
152152
-/
153-
@[expose, inline]
153+
@[expose, inline, deprecated String.ofList (since := "2025-10-30")]
154154
def List.asString (s : List Char) : String :=
155-
String.mk s
155+
String.ofList s
156156

157157
namespace Substring.Internal
158158

src/Init/Data/String/Defs.lean

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,23 @@ theorem String.bytes_inj {s t : String} : s.bytes = t.bytes ↔ s = t := by
108108
subst h
109109
rfl
110110

111-
@[simp] theorem List.bytes_asString {l : List Char} : l.asString.bytes = l.utf8Encode := by
112-
simp [List.asString, String.mk]
111+
@[simp] theorem String.bytes_ofList {l : List Char} : (String.ofList l).bytes = l.utf8Encode := by
112+
simp [String.ofList]
113113

114-
theorem String.exists_eq_asString (s : String) :
115-
∃ l : List Char, s = l.asString := by
114+
@[deprecated String.bytes_ofList (since := "2025-10-30")]
115+
theorem List.bytes_asString {l : List Char} : (String.ofList l).bytes = l.utf8Encode :=
116+
String.bytes_ofList
117+
118+
theorem String.exists_eq_ofList (s : String) :
119+
∃ l : List Char, s = String.ofList l := by
116120
rcases s with ⟨_, ⟨l, rfl⟩⟩
117121
refine ⟨l, by simp [← String.bytes_inj]⟩
118122

123+
@[deprecated String.exists_eq_ofList (since := "2025-10-30")]
124+
theorem String.exists_eq_asString (s : String) :
125+
∃ l : List Char, s = String.ofList l :=
126+
s.exists_eq_ofList
127+
119128
@[simp]
120129
theorem String.utf8ByteSize_empty : "".utf8ByteSize = 0 := (rfl)
121130

@@ -158,9 +167,13 @@ theorem utf8ByteSize_ofByteArray {b : ByteArray} {h} :
158167
theorem bytes_singleton {c : Char} : (String.singleton c).bytes = [c].utf8Encode := by
159168
simp [singleton]
160169

161-
theorem singleton_eq_asString {c : Char} : String.singleton c = [c].asString := by
170+
theorem singleton_eq_ofList {c : Char} : String.singleton c = String.ofList [c] := by
162171
simp [← String.bytes_inj]
163172

173+
@[deprecated singleton_eq_ofList (since := "2025-10-30")]
174+
theorem singleton_eq_asString {c : Char} : String.singleton c = String.ofList [c] :=
175+
singleton_eq_ofList
176+
164177
@[simp]
165178
theorem append_singleton {s : String} {c : Char} : s ++ singleton c = s.push c := by
166179
simp [← bytes_inj]
@@ -586,9 +599,6 @@ def Slice.Pos.byte {s : Slice} (pos : s.Pos) (h : pos ≠ s.endPos) : UInt8 :=
586599

587600
@[simp] theorem default_eq : default = "" := rfl
588601

589-
@[simp]
590-
theorem mk_eq_asString (s : List Char) : String.mk s = List.asString s := rfl
591-
592602
theorem push_eq_append (c : Char) : String.push s c = s ++ singleton c := by
593603
simp
594604

src/Init/Data/String/Lemmas.lean

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ open Std
1919

2020
namespace String
2121

22-
protected theorem data_eq_of_eq {a b : String} (h : a = b) : a.data = b.data :=
22+
@[deprecated toList_inj (since := "2025-10-30")]
23+
protected theorem data_eq_of_eq {a b : String} (h : a = b) : a.toList = b.toList :=
2324
h ▸ rfl
24-
protected theorem ne_of_data_ne {a b : String} (h : a.data ≠ b.data) : a ≠ b :=
25-
fun h' => absurd (String.data_eq_of_eq h') h
25+
@[deprecated toList_inj (since := "2025-10-30")]
26+
protected theorem ne_of_data_ne {a b : String} (h : a.toList ≠ b.toList) : a ≠ b := by
27+
simpa [← toList_inj]
2628

2729
@[simp] protected theorem not_le {a b : String} : ¬ a ≤ b ↔ b < a := Decidable.not_not
2830
@[simp] protected theorem not_lt {a b : String} : ¬ a < b ↔ b ≤ a := Iff.rfl
@@ -34,7 +36,7 @@ attribute [local instance] Char.notLTTrans Char.notLTAntisymm Char.notLTTotal
3436
protected theorem le_trans {a b c : String} : a ≤ b → b ≤ c → a ≤ c := List.le_trans
3537
protected theorem lt_trans {a b c : String} : a < b → b < c → a < c := List.lt_trans
3638
protected theorem le_total (a b : String) : a ≤ b ∨ b ≤ a := List.le_total _ _
37-
protected theorem le_antisymm {a b : String} : a ≤ b → b ≤ a → a = b := fun h₁ h₂ => String.ext (List.le_antisymm (as := a.data) (bs := b.data) h₁ h₂)
39+
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₂)
3840
protected theorem lt_asymm {a b : String} (h : a < b) : ¬ b < a := List.lt_asymm h
3941
protected theorem ne_of_lt {a b : String} (h : a < b) : a ≠ b := by
4042
have := String.lt_irrefl a

src/Init/Data/String/Modify.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,11 @@ Examples:
160160
-/
161161
@[extern "lean_string_utf8_set", expose]
162162
def Pos.Raw.set : String → (@& Pos.Raw) → Char → String
163-
| s, i, c => (Pos.Raw.utf8SetAux c s.data 0 i).asString
163+
| s, i, c => ofList (Pos.Raw.utf8SetAux c s.toList 0 i)
164164

165165
@[extern "lean_string_utf8_set", expose, deprecated Pos.Raw.set (since := "2025-10-14")]
166166
def set : String → (@& Pos.Raw) → Char → String
167-
| s, i, c => (Pos.Raw.utf8SetAux c s.data 0 i).asString
167+
| s, i, c => ofList (Pos.Raw.utf8SetAux c s.toList 0 i)
168168

169169
/--
170170
Replaces the character at position `p` in the string `s` with the result of applying `f` to that

src/Lean/DocString/Parser.lean

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ mutual
621621
asStringFn (atomicFn (noSpaceBefore >> repFn count (satisfyFn (· == char) s!"'{tok count}'"))))
622622

623623
where
624-
tok (count : Nat) : String := (List.replicate count char).asString
624+
tok (count : Nat) : String := String.ofList (List.replicate count char)
625625
opener (ctxt : InlineCtxt) : ParserFn :=
626626
match getter ctxt with
627627
| none => many1Fn (satisfyFn (· == char) s!"any number of {char}s")
@@ -665,7 +665,7 @@ mutual
665665
where
666666
opener : ParserFn := asStringFn (many1Fn (satisfyFn (· == '`') s!"any number of backticks"))
667667
closer (count : Nat) : ParserFn :=
668-
asStringFn (atomicFn (repFn count (satisfyFn' (· == '`') s!"expected '{String.mk (.replicate count '`')}' to close inline code"))) >>
668+
asStringFn (atomicFn (repFn count (satisfyFn' (· == '`') s!"expected '{String.ofList (.replicate count '`')}' to close inline code"))) >>
669669
notFollowedByFn (satisfyFn (· == '`') "`") "backtick"
670670
takeBackticksFn : Nat → ParserFn
671671
| 0 => satisfyFn (fun _ => false)
@@ -1081,7 +1081,7 @@ mutual
10811081
(closeFence l fenceWidth >>
10821082
withFence 0 fun info _ c s =>
10831083
if (c.fileMap.toPosition info.getPos?.get!).column != col then
1084-
s.mkErrorAt s!"closing '{String.mk <| List.replicate fenceWidth ':'}' from directive on line {l} at column {col}, but it's at column {(c.fileMap.toPosition info.getPos?.get!).column}" info.getPos?.get!
1084+
s.mkErrorAt s!"closing '{String.ofList <| List.replicate fenceWidth ':'}' from directive on line {l} at column {col}, but it's at column {(c.fileMap.toPosition info.getPos?.get!).column}" info.getPos?.get!
10851085
else
10861086
s))
10871087

@@ -1114,7 +1114,7 @@ mutual
11141114
else skipFn
11151115

11161116
closeFence (line width : Nat) :=
1117-
let str := String.mk (.replicate width ':')
1117+
let str := String.ofList (.replicate width ':')
11181118
bolThen (description := s!"closing '{str}' for directive from line {line}")
11191119
(eatSpaces >>
11201120
asStringFn (strFn str) >> notFollowedByFn (chFn ':') "':'" >>

src/Lean/Elab/StructInstHint.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def mkMissingFieldsHint (fields : Array (Name × Option Expr)) (stx : Syntax) :
108108
match interveningLineEndPos? with
109109
| none => (.line, .nil)
110110
| some interveningLineEndPos =>
111-
(String.mk (List.replicate (indent - col interveningLineEndPos) ' '), .nil)
111+
(String.ofList (List.replicate (indent - col interveningLineEndPos) ' '), .nil)
112112
let suggestionText := preWs ++ suggestionText ++ postWs
113113
let insPos := view.lastFieldTailPos?.getD <| interveningLineEndPos?.getD view.leaderTailPos
114114
let width := Tactic.TryThis.format.inputWidth.get (← getOptions)

src/Lean/ErrorExplanation.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ where
8686
stringContents : Parser String := attempt do
8787
let escaped := pchar '\\' *> pchar '"'
8888
let cs ← many (notFollowedBy (pchar '"') *> (escaped <|> any))
89-
return String.mk cs.toList
89+
return String.ofList cs.toList
9090

9191
/--
9292
Parses all input up to the next whitespace. If `nonempty` is `true`, fails if there is no input

0 commit comments

Comments
 (0)