//! `PrintLn` must pick a format specifier its value actually supports. //! //! It always emitted `{}`, which needs Display — so wiring a collection into it produced //! a program that could not compile (E0277), and codegen called that a success. The fix //! is deliberately narrow: `{:?}` only for the std types where `{}` is definitely wrong, //! so nothing that works today changes, strings included. use flowrs_core::graph::{Graph, WireMode}; use std::process::Command; fn compiles(src: &str) -> Result<(), String> { let dir = std::env::temp_dir().join("flowrs_println_fmt"); std::fs::create_dir_all(dir.join("src")).unwrap(); std::fs::write( dir.join("Cargo.toml"), "[workspace]\n[package]\nname=\"p\"\nversion=\"0.0.0\"\nedition=\"2021\"\n", ) .unwrap(); std::fs::write(dir.join("src/main.rs"), src).unwrap(); let out = Command::new("cargo") .args(["check", "--quiet", "--offline", "--message-format=short"]) .current_dir(&dir) // `CARGO_TARGET_DIR` removed so this check does not depend on ambient config. // // `graph_build` in flowrs-mcp had a demonstrated false pass from an inherited // target dir: its scratch crates are all `flowrs_graph 0.0.0`, and one shared // target made them a single build unit, so cargo found a later program already // fresh and exited 0 without reading it. These harnesses use the same package // identity, so the same shape is possible here. // // Tested, and it did NOT reproduce: priming a shared target with a passing // harness and then running this one with its own codegen broken still failed // correctly. This is isolation against a hazard that has bitten once nearby, // not a fix for a bug demonstrated here. .env_remove("CARGO_TARGET_DIR") .output() .unwrap(); if out.status.success() { return Ok(()); } let e = String::from_utf8_lossy(&out.stderr); if e.contains("failed to download") || e.contains("no matching package") { return Ok(()); // environment limit, not a defect } Err(e .lines() .filter(|l| l.contains("error")) .take(4) .collect::>() .join("\n")) } fn lit(g: &mut Graph, ty: &str, v: &str) -> flowrs_core::NodeId { let n = g.add_node(ty, 0.0, 0.0); g.nodes .get_mut(&n) .unwrap() .properties .insert("value".into(), v.into()); n } /// The bug: a Vec has no Display, so `{}` could never work. #[test] fn a_collection_printed_uses_debug_and_compiles() { let mut g = Graph::new(); let a = lit(&mut g, "IntLiteral", "0"); let b = lit(&mut g, "IntLiteral", "4"); let r = g.add_node("Range", 0.0, 0.0); g.add_wire(a, "value", r, "start", WireMode::Move).unwrap(); g.add_wire(b, "value", r, "end", WireMode::Move).unwrap(); let c = g.add_node("Collect", 0.0, 0.0); g.add_wire(r, "iterator", c, "iterator", WireMode::Move) .unwrap(); let p = g.add_node("PrintLn", 0.0, 0.0); g.add_wire(c, "collection", p, "input", WireMode::Move) .unwrap(); let src = g.to_rust().unwrap(); assert!( src.contains("println!(\"{:?}\""), "a Vec needs Debug:\n{src}" ); if let Err(e) = compiles(&src) { panic!("printing a collection must compile:\n{e}\n---\n{src}"); } } /// The other direction, and the reason this is not a blanket switch to `{:?}`: a string /// printed with Debug gains quotes, which would change the output of every graph that /// already works. #[test] fn a_string_still_prints_with_display_and_no_quotes() { let mut g = Graph::new(); let s = lit(&mut g, "StringLiteral", "hello"); let p = g.add_node("PrintLn", 0.0, 0.0); g.add_wire(s, "value", p, "input", WireMode::Move).unwrap(); let src = g.to_rust().unwrap(); assert!( src.contains("println!(\"{}\""), "a string must keep Display:\n{src}" ); } /// A scalar keeps Display too — the common case must be untouched. #[test] fn a_scalar_keeps_display() { let mut g = Graph::new(); let n = lit(&mut g, "IntLiteral", "7"); let p = g.add_node("PrintLn", 0.0, 0.0); g.add_wire(n, "value", p, "input", WireMode::Move).unwrap(); assert!(g.to_rust().unwrap().contains("println!(\"{}\"")); } /// An unwired PrintLn is unchanged: nothing upstream says otherwise, so it keeps `{}`. #[test] fn an_unconnected_println_is_unchanged() { let mut g = Graph::new(); g.add_node("PrintLn", 0.0, 0.0); assert!(g.to_rust().unwrap().contains("println!(\"{}\"")); }