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>
8094353 parent: 5e05c89 modified
src/lower.rs +31 -11 | @@ -3211,7 +3211,7 @@ impl Lowerer { | ||
| 3211 | 3211 | // information and the declaration lowers to nothing. |
| 3212 | 3212 | Item::ExternCrate(_) => Ok(()), |
| 3213 | 3213 | Item::Const(c) => { |
| 3214 | - let t = self.map_ty(&c.ty)?.owned(); | |
| 3214 | + let t = owned_strings(self.map_ty(&c.ty)?); | |
| 3215 | 3215 | // The annotation types the initialiser, exactly as it does for |
| 3216 | 3216 | // a `let`: `const MOD: u32 = 65521` is a u32 literal. |
| 3217 | 3217 | let v = self.expr_at(&c.expr, Some(&t))?; |
| @@ -3490,16 +3490,7 @@ impl Lowerer { | ||
| 3490 | 3490 | { |
| 3491 | 3491 | return Ok(()); |
| 3492 | 3492 | } |
| 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)?); | |
| 3503 | 3494 | let v = self.expr_at(&c.expr, Some(&t))?; |
| 3504 | 3495 | let name = format!("{}_{}", tyname, c.ident); |
| 3505 | 3496 | self.line(&format!("const {}*: {} = {}", ident(&name), t.render(), v.code)); |
| @@ -8509,6 +8500,17 @@ impl Lowerer { | ||
| 8509 | 8500 | } |
| 8510 | 8501 | |
| 8511 | 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 | 8514 | // Terminal iterator operations: they consume the chain rather than |
| 8513 | 8515 | // adapting it, so they become the loop instead of joining it. |
| 8514 | 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 | 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 | +} | |
| @@ -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 for | 3215 | // 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 of | 3493 | + 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 than | 8514 | // 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 | +} | ||