//! The collection, channel and struct nodes must emit Rust that actually compiles. //! //! Emitting *something* is not the bar — `node_coverage` measures that, and it passed for //! these long before they worked. This compiles the whole emitted program, which is what //! caught every bug in them: bare `map`/`tx`/`instance` identifiers where an input was not //! connected (E0425), `HashMap::new()` with nothing to infer from (E0282), a struct type //! nothing declared (E0422), and a `FieldSet` assigning through a non-`mut` binding //! (E0594). Each of those emitted confidently and failed only at `rustc`. use flowrs_core::graph::{Graph, WireMode}; use std::process::Command; fn check(label: &str, g: &Graph) { let src = match g.to_rust() { Ok(s) => s, Err(e) => panic!("{label} should emit, but codegen refused: {e}"), }; let dir = std::env::temp_dir().join("flowrs_probe_wired"); 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; } let e = String::from_utf8_lossy(&out.stderr); // A dependency that will not resolve offline is an environment limit, not a defect. if e.contains("failed to download") || e.contains("no matching package") { eprintln!("skipped {label}: dependencies unavailable offline"); return; } let diags: Vec<&str> = e.lines().filter(|l| l.contains("error")).take(6).collect(); panic!( "{label} emits Rust that does not compile:\n {}\n--- source ---\n{src}", diags.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 } #[test] fn the_collection_channel_and_struct_nodes_compile_when_wired() { // HashMapNew -> HashMapInsert -> HashMapGet let mut g = Graph::new(); let m = g.add_node("HashMapNew", 0.0, 0.0); let k = lit(&mut g, "StringLiteral", "alpha"); let v = lit(&mut g, "IntLiteral", "7"); let ins = g.add_node("HashMapInsert", 0.0, 0.0); g.add_wire(m, "map", ins, "map", WireMode::Move).unwrap(); g.add_wire(k, "value", ins, "key", WireMode::Move).unwrap(); g.add_wire(v, "value", ins, "value", WireMode::Move) .unwrap(); check("HashMapInsert(wired)", &g); let k2 = lit(&mut g, "StringLiteral", "alpha"); let get = g.add_node("HashMapGet", 0.0, 0.0); g.add_wire(ins, "map", get, "map", WireMode::Move).unwrap(); g.add_wire(k2, "value", get, "key", WireMode::Move).unwrap(); check("HashMapGet(wired)", &g); // ChannelNew -> Send / Recv let mut g = Graph::new(); let ch = g.add_node("ChannelNew", 0.0, 0.0); let val = lit(&mut g, "IntLiteral", "3"); let snd = g.add_node("Send", 0.0, 0.0); g.add_wire(ch, "sender", snd, "sender", WireMode::Move) .unwrap(); g.add_wire(val, "value", snd, "value", WireMode::Move) .unwrap(); let rcv = g.add_node("Recv", 0.0, 0.0); g.add_wire(ch, "receiver", rcv, "receiver", WireMode::Move) .unwrap(); check("Send+Recv(wired)", &g); // StructNew -> FieldGet / FieldSet let mut g = Graph::new(); let sn = g.add_node("StructNew", 0.0, 0.0); { let p = &mut g.nodes.get_mut(&sn).unwrap().properties; p.insert("type".into(), "Settings".into()); p.insert("gain".into(), "3".into()); p.insert("name".into(), "probe".into()); } let fg = g.add_node("FieldGet", 0.0, 0.0); g.nodes .get_mut(&fg) .unwrap() .properties .insert("field".into(), "gain".into()); g.add_wire(sn, "instance", fg, "instance", WireMode::Move) .unwrap(); check("FieldGet(wired)", &g); let mut g2 = Graph::new(); let sn2 = g2.add_node("StructNew", 0.0, 0.0); { let p = &mut g2.nodes.get_mut(&sn2).unwrap().properties; p.insert("type".into(), "Settings".into()); p.insert("gain".into(), "3".into()); } let nv = lit(&mut g2, "IntLiteral", "9"); let fs = g2.add_node("FieldSet", 0.0, 0.0); g2.nodes .get_mut(&fs) .unwrap() .properties .insert("field".into(), "gain".into()); g2.add_wire(sn2, "instance", fs, "instance", WireMode::Move) .unwrap(); g2.add_wire(nv, "value", fs, "value", WireMode::Move) .unwrap(); check("FieldSet(wired)", &g2); }