nandi/rustnimpublic Fork
⑂ 8094353
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

Lower `write_fmt` on a formatter, and own the strings in a constant

`f.write_fmt(format_args!(..))` is what `write!(f, ..)` becomes once macros
are expanded, and every derived `Debug` in an expanded crate is written that
way. Inside a formatting impl the proc accumulates into `result`, so this is
an append -- the same thing `write_str` already did, which is why the first
attempt at this shadowed `write_str` and broke `026-base16ct-crate`. The
branch is narrowed to the one spelling that was missing.

`const NAMES: [&str; 3]` owns its strings. `&str` is a view of a buffer
everywhere it can borrow from one, and a constant cannot -- so every view of
characters inside a constant's type becomes a string, not just one at the top
level. That extends the fix two commits ago from `&'static str` to arrays and
tuples of them.

tests/cases/078.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-20T14:06:31-07:00 Browse files
8094353 parent: 5e05c89
modified src/lower.rs +31 -11
@@ -3211,7 +3211,7 @@ impl Lowerer {
32113211 // information and the declaration lowers to nothing.
32123212 Item::ExternCrate(_) => Ok(()),
32133213 Item::Const(c) => {
3214- let t = self.map_ty(&c.ty)?.owned();
3214+ let t = owned_strings(self.map_ty(&c.ty)?);
32153215 // The annotation types the initialiser, exactly as it does for
32163216 // a `let`: `const MOD: u32 = 65521` is a u32 literal.
32173217 let v = self.expr_at(&c.expr, Some(&t))?;
@@ -3490,16 +3490,7 @@ impl Lowerer {
34903490 {
34913491 return Ok(());
34923492 }
3493- // A `&'static str` constant is an owned string, not a borrowed view of
3494- // one: there is no buffer for it to borrow from, and `owned()` on a
3495- // view of characters gives `seq[char]`, which is not what a string
3496- // literal is.
3497- let t = match self.map_ty(&c.ty)? {
3498- Nim::OpenArray(e) if matches!(&*e, Nim::Prim(p) if p == "char") => {
3499- Nim::Prim("string".into())
3500- }
3501- other => other.owned(),
3502- };
3493+ let t = owned_strings(self.map_ty(&c.ty)?);
35033494 let v = self.expr_at(&c.expr, Some(&t))?;
35043495 let name = format!("{}_{}", tyname, c.ident);
35053496 self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));
@@ -8509,6 +8500,17 @@ impl Lowerer {
85098500 }
85108501
85118502 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {
8503+ // `f.write_fmt(format_args!(..))` is what `write!(f, ..)` becomes once
8504+ // macros are expanded. Inside a formatting impl the proc accumulates
8505+ // into `result`, so this is an append -- the same thing `write_str`
8506+ // already does a few branches down, which handles the argument's view
8507+ // types and is left to.
8508+ if m.method == "write_fmt" && m.args.len() == 1 && self.is_fmt_param(&m.receiver) {
8509+ let v = self.expr(&m.args[0])?;
8510+ self.line(&format!("result.add({})", v.code));
8511+ return Ok(Val::new(String::new(), Some(Nim::Unit)));
8512+ }
8513+
85128514 // Terminal iterator operations: they consume the chain rather than
85138515 // adapting it, so they become the loop instead of joining it.
85148516 if matches!(m.method.to_string().as_str(), "find" | "any" | "all" | "position") {
@@ -10930,3 +10932,21 @@ fn if_let_as_match(i: &syn::ExprIf) -> Expr {
1093010932 arms,
1093110933 })
1093210934 }
10935+
10936+/// Turn every borrowed view of characters in a type into an owned string.
10937+///
10938+/// `&str` is a view of a buffer everywhere it can borrow from one. A constant
10939+/// cannot: `const NAMES: [&str; 3]` owns its strings, and `owned()` alone
10940+/// would make them `seq[char]`, which is not what a string literal is.
10941+fn owned_strings(t: Nim) -> Nim {
10942+ match t {
10943+ Nim::OpenArray(e) if matches!(&*e, Nim::Prim(p) if p == "char") => {
10944+ Nim::Prim("string".into())
10945+ }
10946+ Nim::Array(n, e) => Nim::Array(n, Box::new(owned_strings(*e))),
10947+ Nim::Seq(e) | Nim::OpenArray(e) => Nim::Seq(Box::new(owned_strings(*e))),
10948+ Nim::Tuple(ts) => Nim::Tuple(ts.into_iter().map(owned_strings).collect()),
10949+ Nim::Named(n, a) => Nim::Named(n, a.into_iter().map(owned_strings).collect()),
10950+ other => other.owned(),
10951+ }
10952+}
@@ -3211,7 +3211,7 @@ impl Lowerer {
3211 // information and the declaration lowers to nothing.3211 // information and the declaration lowers to nothing.
3212 Item::ExternCrate(_) => Ok(()),3212 Item::ExternCrate(_) => Ok(()),
3213 Item::Const(c) => {3213 Item::Const(c) => {
3214- let t = self.map_ty(&c.ty)?.owned();3214+ let t = owned_strings(self.map_ty(&c.ty)?);
3215 // The annotation types the initialiser, exactly as it does for3215 // The annotation types the initialiser, exactly as it does for
3216 // a `let`: `const MOD: u32 = 65521` is a u32 literal.3216 // a `let`: `const MOD: u32 = 65521` is a u32 literal.
3217 let v = self.expr_at(&c.expr, Some(&t))?;3217 let v = self.expr_at(&c.expr, Some(&t))?;
@@ -3490,16 +3490,7 @@ impl Lowerer {
3490 {3490 {
3491 return Ok(());3491 return Ok(());
3492 }3492 }
3493- // A `&'static str` constant is an owned string, not a borrowed view of3493+ let t = owned_strings(self.map_ty(&c.ty)?);
3494- // one: there is no buffer for it to borrow from, and `owned()` on a
3495- // view of characters gives `seq[char]`, which is not what a string
3496- // literal is.
3497- let t = match self.map_ty(&c.ty)? {
3498- Nim::OpenArray(e) if matches!(&*e, Nim::Prim(p) if p == "char") => {
3499- Nim::Prim("string".into())
3500- }
3501- other => other.owned(),
3502- };
3503 let v = self.expr_at(&c.expr, Some(&t))?;3494 let v = self.expr_at(&c.expr, Some(&t))?;
3504 let name = format!("{}_{}", tyname, c.ident);3495 let name = format!("{}_{}", tyname, c.ident);
3505 self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));3496 self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code));
@@ -8509,6 +8500,17 @@ impl Lowerer {
8509 }8500 }
8510 8501
8511 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {8502 fn method(&mut self, m: &syn::ExprMethodCall, expect: Option<&Nim>) -> Result<Val, String> {
8503+ // `f.write_fmt(format_args!(..))` is what `write!(f, ..)` becomes once
8504+ // macros are expanded. Inside a formatting impl the proc accumulates
8505+ // into `result`, so this is an append -- the same thing `write_str`
8506+ // already does a few branches down, which handles the argument's view
8507+ // types and is left to.
8508+ if m.method == "write_fmt" && m.args.len() == 1 && self.is_fmt_param(&m.receiver) {
8509+ let v = self.expr(&m.args[0])?;
8510+ self.line(&format!("result.add({})", v.code));
8511+ return Ok(Val::new(String::new(), Some(Nim::Unit)));
8512+ }
8513+
8512 // Terminal iterator operations: they consume the chain rather than8514 // Terminal iterator operations: they consume the chain rather than
8513 // adapting it, so they become the loop instead of joining it.8515 // adapting it, so they become the loop instead of joining it.
8514 if matches!(m.method.to_string().as_str(), "find" | "any" | "all" | "position") {8516 if matches!(m.method.to_string().as_str(), "find" | "any" | "all" | "position") {
@@ -10930,3 +10932,21 @@ fn if_let_as_match(i: &syn::ExprIf) -> Expr {
10930 arms,10932 arms,
10931 })10933 })
10932 }10934 }
10935+
10936+/// Turn every borrowed view of characters in a type into an owned string.
10937+///
10938+/// `&str` is a view of a buffer everywhere it can borrow from one. A constant
10939+/// cannot: `const NAMES: [&str; 3]` owns its strings, and `owned()` alone
10940+/// would make them `seq[char]`, which is not what a string literal is.
10941+fn owned_strings(t: Nim) -> Nim {
10942+ match t {
10943+ Nim::OpenArray(e) if matches!(&*e, Nim::Prim(p) if p == "char") => {
10944+ Nim::Prim("string".into())
10945+ }
10946+ Nim::Array(n, e) => Nim::Array(n, Box::new(owned_strings(*e))),
10947+ Nim::Seq(e) | Nim::OpenArray(e) => Nim::Seq(Box::new(owned_strings(*e))),
10948+ Nim::Tuple(ts) => Nim::Tuple(ts.into_iter().map(owned_strings).collect()),
10949+ Nim::Named(n, a) => Nim::Named(n, a.into_iter().map(owned_strings).collect()),
10950+ other => other.owned(),
10951+ }
10952+}
added tests/cases/078-write-fmt.rs +36 -0
new file mode 100644
@@ -0,0 +1,36 @@
1+// `f.write_fmt(format_args!(..))` is what `write!(f, ..)` becomes once macros
2+// are expanded, and `write_str` is the same without the formatting. Inside a
3+// formatting impl the proc accumulates, so both are appends — and they answer
4+// `fmt::Result`, which is always `Ok` here because appending cannot fail.
5+use std::fmt;
6+
7+struct Flags {
8+ bits: u8,
9+}
10+
11+const NAMES: [&str; 3] = ["BOLD", "DIM", "ITALIC"];
12+
13+impl fmt::Debug for Flags {
14+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15+ f.write_str("Flags(")?;
16+ let mut first = true;
17+ for i in 0..3 {
18+ if self.bits & (1 << i) != 0 {
19+ if !first {
20+ f.write_str(" | ")?;
21+ }
22+ f.write_fmt(format_args!("{}", NAMES[i]))?;
23+ first = false;
24+ }
25+ }
26+ f.write_str(")")?;
27+ Ok(())
28+ }
29+}
30+
31+fn main() {
32+ println!("{:?}", Flags { bits: 0 });
33+ println!("{:?}", Flags { bits: 1 });
34+ println!("{:?}", Flags { bits: 5 });
35+ println!("{:?}", Flags { bits: 7 });
36+}
new file mode 100644
@@ -0,0 +1,36 @@
1+// `f.write_fmt(format_args!(..))` is what `write!(f, ..)` becomes once macros
2+// are expanded, and `write_str` is the same without the formatting. Inside a
3+// formatting impl the proc accumulates, so both are appends — and they answer
4+// `fmt::Result`, which is always `Ok` here because appending cannot fail.
5+use std::fmt;
6+
7+struct Flags {
8+ bits: u8,
9+}
10+
11+const NAMES: [&str; 3] = ["BOLD", "DIM", "ITALIC"];
12+
13+impl fmt::Debug for Flags {
14+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15+ f.write_str("Flags(")?;
16+ let mut first = true;
17+ for i in 0..3 {
18+ if self.bits & (1 << i) != 0 {
19+ if !first {
20+ f.write_str(" | ")?;
21+ }
22+ f.write_fmt(format_args!("{}", NAMES[i]))?;
23+ first = false;
24+ }
25+ }
26+ f.write_str(")")?;
27+ Ok(())
28+ }
29+}
30+
31+fn main() {
32+ println!("{:?}", Flags { bits: 0 });
33+ println!("{:?}", Flags { bits: 1 });
34+ println!("{:?}", Flags { bits: 5 });
35+ println!("{:?}", Flags { bits: 7 });
36+}