Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions core/daglang/daglang-lower/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5678,13 +5678,13 @@ fn add_service_call_edges(
known_interface_types: &HashSet<String>,
data_values: &HashMap<String, serde_json::Value>,
) -> Result<(), LowerError> {
// Track transport endpoint usage across ALL callables in the compiled
// graph so the second callsite (even in a different module) to reference
// the same service operation gets a cloned triplet (_c1, _c2, …) instead
// of wiring duplicate scalar edges to the original.
let mut endpoint_use_count: HashMap<String, usize> = HashMap::new();
for module in &project.modules {
let module_name = module.module_path.as_dotted();
// Track transport endpoint usage across ALL callables in the module so
// that the second callable to reference the same service operation gets
// a cloned triplet (_c1, _c2, …) instead of wiring duplicate scalar
// edges to the original.
let mut endpoint_use_count: HashMap<String, usize> = HashMap::new();
for item in &module.ast.items {
let (item_name, params, stmts, uses_binding_types, body_lossy) = match &item.node {
Item::FnDef(def) => (
Expand Down
69 changes: 69 additions & 0 deletions core/daglang/daglang-lower/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2973,6 +2973,75 @@ func dual(msg: String) -> { reply: String } {
);
}

#[test]
fn cross_module_service_dedup_clones_transport_triplet() {
// Regression for BT-E1: two callables in different modules that invoke the
// same service operation must not both wire scalar inputs to the original
// prepare node.
let typed = typed_project_from_sources(&[
(
"dsl/services/dedup_shared.dag",
r#"module sample.shared
service Echo {
operation Ping(message: String) -> { reply: String } {
transport shell { argv: ["echo", "{message}"] }
}
}"#,
),
(
"dsl/services/dedup_alpha.dag",
r#"module sample.alpha
func alpha() -> { reply: String } {
result = sample.shared.Echo.Ping(message: "alpha")
return { reply: result.reply }
}"#,
),
(
"dsl/services/dedup_beta.dag",
r#"module sample.beta
func beta() -> { reply: String } {
result = sample.shared.Echo.Ping(message: "beta")
return { reply: result.reply }
}"#,
),
]);
let dag = lower_typed_project(&typed).expect("lowering should succeed");

let prepare_nodes: Vec<_> = dag
.nodes
.iter()
.filter(|n| {
n.id.0.starts_with("prepare_transport_")
&& n.id.0.contains("sample_shared_Echo_Ping")
})
.collect();
assert!(
prepare_nodes.len() >= 2,
"expected at least 2 prepare nodes for sample.shared.Echo.Ping (original + clone), found {}: {:?}",
prepare_nodes.len(),
prepare_nodes.iter().map(|n| &n.id.0).collect::<Vec<_>>()
);

// Ensure no scalar destination gets multiple upstream edges.
let mut edge_targets: std::collections::HashMap<(String, String), Vec<String>> =
std::collections::HashMap::new();
for edge in &dag.edges {
edge_targets
.entry((edge.to_node.0.clone(), edge.to_port.0.clone()))
.or_default()
.push(edge.from_node.0.clone());
}
for ((node, port), sources) in &edge_targets {
if port == PortName::DEPS || port == "request" || port == "response" {
continue;
}
assert!(
sources.len() <= 1,
"duplicate scalar edge to {node}:{port} from {sources:?}",
);
}
}

// ── Pattern expansion tests ──────────────────────────────────────

/// Lower a synthetic test module injected into the real DSL corpus.
Expand Down
Loading