flowrs-engine/crates/flowrs-core/tests/println_format.rs
flowRS fd958c0547 flowRS engine, cut from flowRS 3451b065
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 19:04:39 -05:00

120 lines
4.5 KiB
Rust

//! `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::<Vec<_>>()
.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!(\"{}\""));
}