1use super::{
2 BTreeMap, DirectPropagation, FallibleKind, Language, OPTIONAL_VALIDATION_RULE, OperationKind,
3 OperationNode, RESULT_VALIDATION_RULE, SOG_SCHEMA_VERSION, SemanticCandidateConfig,
4 SemanticCandidatePair, SemanticOperationGraph, SemanticRule, SemanticRuleMatcher,
5 SemanticRulePattern, SemanticRuleScope, compatible_fallible_kinds, compatible_type_tags,
6 direct_construct_matches, only_api_name,
7};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct CrossLanguageApiCorrespondence {
16 pub id: &'static str,
18 pub operation: OperationKind,
20 pub rust_api_names: &'static [&'static str],
22 pub cpp_api_names: &'static [&'static str],
24}
25
26const CROSS_LANGUAGE_API_CORRESPONDENCES: &[CrossLanguageApiCorrespondence] = &[
27 CrossLanguageApiCorrespondence {
28 id: "sequence-source-v1",
29 operation: OperationKind::Source,
30 rust_api_names: &["rust::IntoIterator::into_iter", "rust::slice::iter"],
31 cpp_api_names: &["std::begin"],
32 },
33 CrossLanguageApiCorrespondence {
34 id: "sequence-filter-v1",
35 operation: OperationKind::Filter,
36 rust_api_names: &["rust::Iterator::filter"],
37 cpp_api_names: &["std::copy_if"],
38 },
39 CrossLanguageApiCorrespondence {
40 id: "sequence-map-v1",
41 operation: OperationKind::Map,
42 rust_api_names: &["rust::Iterator::map"],
43 cpp_api_names: &["std::transform"],
44 },
45 CrossLanguageApiCorrespondence {
46 id: "sequence-reduce-v1",
47 operation: OperationKind::Reduce,
48 rust_api_names: &["rust::Iterator::fold"],
49 cpp_api_names: &["std::accumulate"],
50 },
51 CrossLanguageApiCorrespondence {
52 id: "sequence-collect-v1",
53 operation: OperationKind::Collect,
54 rust_api_names: &["rust::Iterator::collect", "rust::Vec::push"],
55 cpp_api_names: &["std::push_back"],
56 },
57];
58
59#[must_use]
61pub const fn cross_language_api_correspondences() -> &'static [CrossLanguageApiCorrespondence] {
62 CROSS_LANGUAGE_API_CORRESPONDENCES
63}
64
65#[must_use]
70pub fn cross_language_api_correspondence(
71 language: Language,
72 api_name: &str,
73) -> Option<&'static CrossLanguageApiCorrespondence> {
74 CROSS_LANGUAGE_API_CORRESPONDENCES
75 .iter()
76 .find(|entry| match language {
77 Language::Rust => entry.rust_api_names.contains(&api_name),
78 Language::Cpp => entry.cpp_api_names.contains(&api_name),
79 Language::C => false,
80 })
81}
82
83pub(super) const CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE: SemanticRule = SemanticRule {
84 id: "cross-language-sequence-pipeline-v1",
85 version: 1,
86 confidence: 0.55,
87 scope: SemanticRuleScope::RustCpp,
88 pattern: SemanticRulePattern {
89 minimum_operations: 2,
90 permitted_kinds: &[
91 OperationKind::Source,
92 OperationKind::Filter,
93 OperationKind::Map,
94 OperationKind::Reduce,
95 OperationKind::Collect,
96 ],
97 },
98 matcher: SemanticRuleMatcher::EquivalentSequence,
99};
100
101pub(super) const CROSS_LANGUAGE_OPTIONAL_VALIDATION_RULE: SemanticRule = SemanticRule {
102 id: "cross-language-optional-validation-v1",
103 version: 1,
104 confidence: 0.55,
105 scope: SemanticRuleScope::RustCpp,
106 pattern: SemanticRulePattern {
107 minimum_operations: 1,
108 permitted_kinds: &[OperationKind::Validate],
109 },
110 matcher: SemanticRuleMatcher::DirectConstruct {
111 kind: OperationKind::Validate,
112 fallible_kind: FallibleKind::Option,
113 direct_propagation: None,
114 },
115};
116
117pub(super) const CROSS_LANGUAGE_RESULT_VALIDATION_RULE: SemanticRule = SemanticRule {
118 id: "cross-language-result-validation-v1",
119 version: 1,
120 confidence: 0.55,
121 scope: SemanticRuleScope::RustCpp,
122 pattern: SemanticRulePattern {
123 minimum_operations: 1,
124 permitted_kinds: &[OperationKind::Validate],
125 },
126 matcher: SemanticRuleMatcher::DirectConstruct {
127 kind: OperationKind::Validate,
128 fallible_kind: FallibleKind::Result,
129 direct_propagation: None,
130 },
131};
132
133pub(super) const CROSS_LANGUAGE_RESULT_DIRECT_PROPAGATION_RULE: SemanticRule = SemanticRule {
134 id: "cross-language-result-direct-propagation-v1",
135 version: 1,
136 confidence: 0.55,
137 scope: SemanticRuleScope::RustCpp,
138 pattern: SemanticRulePattern {
139 minimum_operations: 1,
140 permitted_kinds: &[OperationKind::PropagateError],
141 },
142 matcher: SemanticRuleMatcher::DirectConstruct {
143 kind: OperationKind::PropagateError,
144 fallible_kind: FallibleKind::Result,
145 direct_propagation: Some(DirectPropagation::ResultAdapter),
146 },
147};
148
149pub(super) const OPTIONAL_VALIDATION_CORRESPONDENCE_ID: &str = "optional-presence-validation-v1";
154
155pub(super) const RESULT_VALIDATION_CORRESPONDENCE_ID: &str = "result-expected-validation-v1";
159
160pub(super) const RESULT_DIRECT_PROPAGATION_CORRESPONDENCE_ID: &str =
164 "result-expected-direct-propagation-v1";
165
166#[derive(Debug, Clone, PartialEq, Eq)]
173pub struct CrossLanguageCandidateInput {
174 pub comparison_partition: [u8; 16],
176 pub graph: SemanticOperationGraph,
178}
179
180#[derive(Debug, Clone, Default, PartialEq, Eq)]
182pub struct CrossLanguageCandidateStats {
183 pub graphs: usize,
185 pub ineligible_graphs: usize,
188 pub buckets: usize,
190 pub oversized_buckets: usize,
192 pub pairs_available: usize,
194 pub pairs_budget_dropped: usize,
196 pub pairs_emitted: usize,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct CrossLanguageCandidateExtraction {
203 pub pairs: Vec<SemanticCandidatePair>,
205 pub stats: CrossLanguageCandidateStats,
207}
208
209#[derive(Debug, Clone, PartialEq)]
212pub struct CrossLanguageRuleMatch {
213 pub rule: SemanticRule,
215 pub correspondence_ids: Vec<&'static str>,
218}
219
220#[must_use]
230pub fn extract_cross_language_candidates(
231 inputs: &[CrossLanguageCandidateInput],
232 config: SemanticCandidateConfig,
233) -> CrossLanguageCandidateExtraction {
234 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
235 struct CandidateKey {
236 comparison_partition: [u8; 16],
237 operations: Vec<OperationKind>,
238 }
239
240 #[derive(Default)]
241 struct Bucket {
242 rust: Vec<usize>,
243 cpp: Vec<usize>,
244 }
245
246 let mut stats = CrossLanguageCandidateStats {
247 graphs: inputs.len(),
248 ..CrossLanguageCandidateStats::default()
249 };
250 let mut index: BTreeMap<CandidateKey, Bucket> = BTreeMap::new();
251 for (index_in_input, input) in inputs.iter().enumerate() {
252 let graph = &input.graph;
253 let pipeline = CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE.pattern.accepts(graph)
254 && graph.nodes.iter().all(|node| {
255 node.attributes.api_names.len() == 1
256 && node.attributes.api_names.iter().next().is_some_and(|api| {
257 cross_language_api_correspondence(graph.language, api)
258 .is_some_and(|entry| entry.operation == node.kind)
259 })
260 });
261 let direct_loop_pipeline = is_cross_language_direct_loop(graph);
262 let optional_validation = is_optional_validation(graph);
263 let result_validation = is_result_validation(graph);
264 let result_direct_propagation = is_result_direct_propagation(graph);
265 if graph.schema_version != SOG_SCHEMA_VERSION
266 || !matches!(graph.language, Language::Rust | Language::Cpp)
267 || !(pipeline
268 || direct_loop_pipeline
269 || optional_validation
270 || result_validation
271 || result_direct_propagation)
272 {
273 stats.ineligible_graphs += 1;
274 continue;
275 }
276 let bucket = index
277 .entry(CandidateKey {
278 comparison_partition: input.comparison_partition,
279 operations: graph.nodes.iter().map(|node| node.kind).collect(),
280 })
281 .or_default();
282 match graph.language {
283 Language::Rust => bucket.rust.push(index_in_input),
284 Language::Cpp => bucket.cpp.push(index_in_input),
285 Language::C => unreachable!("C graphs are excluded before indexing"),
286 }
287 }
288 stats.buckets = index.len();
289
290 let mut pairs = Vec::new();
291 for bucket in index.into_values() {
292 let members = bucket.rust.len().saturating_add(bucket.cpp.len());
293 if members > config.max_bucket_members {
294 stats.oversized_buckets += 1;
295 continue;
296 }
297 let available = bucket.rust.len().saturating_mul(bucket.cpp.len());
298 stats.pairs_available = stats.pairs_available.saturating_add(available);
299 if pairs.len().saturating_add(available) > config.max_candidate_pairs {
300 stats.pairs_budget_dropped = stats.pairs_budget_dropped.saturating_add(available);
301 continue;
302 }
303 for rust in &bucket.rust {
304 for cpp in &bucket.cpp {
305 pairs.push(SemanticCandidatePair {
306 left: (*rust).min(*cpp),
307 right: (*rust).max(*cpp),
308 });
309 }
310 }
311 }
312 pairs.sort_unstable();
313 stats.pairs_emitted = pairs.len();
314 CrossLanguageCandidateExtraction { pairs, stats }
315}
316
317#[must_use]
319pub fn verify_cross_language_candidates(
320 inputs: &[CrossLanguageCandidateInput],
321 candidates: &[SemanticCandidatePair],
322) -> Vec<(SemanticCandidatePair, CrossLanguageRuleMatch)> {
323 candidates
324 .iter()
325 .filter_map(|&candidate| {
326 let (Some(left), Some(right)) =
327 (inputs.get(candidate.left), inputs.get(candidate.right))
328 else {
329 return None;
330 };
331 (left.comparison_partition == right.comparison_partition)
332 .then(|| {
333 match_cross_language_pipeline(&left.graph, &right.graph).or_else(|| {
334 match_cross_language_optional_validation(&left.graph, &right.graph)
335 .or_else(|| {
336 match_cross_language_result_validation(&left.graph, &right.graph)
337 })
338 .or_else(|| {
339 match_cross_language_result_direct_propagation(
340 &left.graph,
341 &right.graph,
342 )
343 })
344 })
345 })
346 .flatten()
347 .map(|rule_match| (candidate, rule_match))
348 })
349 .collect()
350}
351
352#[must_use]
360pub fn match_cross_language_pipeline(
361 left: &SemanticOperationGraph,
362 right: &SemanticOperationGraph,
363) -> Option<CrossLanguageRuleMatch> {
364 let rust_and_cpp = matches!(
365 (left.language, right.language),
366 (Language::Rust, Language::Cpp) | (Language::Cpp, Language::Rust)
367 );
368 if CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE.scope != SemanticRuleScope::RustCpp
369 || left.schema_version != SOG_SCHEMA_VERSION
370 || right.schema_version != SOG_SCHEMA_VERSION
371 || !rust_and_cpp
372 || left.build_variant_fingerprint == right.build_variant_fingerprint
373 || left.nodes.len() != right.nodes.len()
374 || !CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE.pattern.accepts(left)
375 || !CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE.pattern.accepts(right)
376 {
377 return None;
378 }
379 if is_cross_language_direct_loop(left)
380 && is_cross_language_direct_loop(right)
381 && left
382 .nodes
383 .iter()
384 .zip(&right.nodes)
385 .all(|(left_node, right_node)| left_node.kind == right_node.kind)
386 {
387 return Some(CrossLanguageRuleMatch {
388 rule: CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE,
389 correspondence_ids: vec![DIRECT_LOOP_SEQUENCE_CORRESPONDENCE_ID],
390 });
391 }
392
393 let mut correspondence_ids = Vec::with_capacity(left.nodes.len());
394 for (left_node, right_node) in left.nodes.iter().zip(&right.nodes) {
395 if left_node.kind != right_node.kind
396 || !compatible_type_tags(
397 left_node.attributes.type_tag,
398 right_node.attributes.type_tag,
399 )
400 || !compatible_fallible_kinds(
401 left_node.attributes.fallible_kind,
402 right_node.attributes.fallible_kind,
403 )
404 {
405 return None;
406 }
407 let left_api = only_api_name(left_node)?;
408 let right_api = only_api_name(right_node)?;
409 let left_entry = cross_language_api_correspondence(left.language, left_api)?;
410 let right_entry = cross_language_api_correspondence(right.language, right_api)?;
411 let correspondence_matches = left_entry.id == right_entry.id
412 && correspondence_covers(left_entry, left_node)
413 && correspondence_covers(right_entry, right_node);
414 if !correspondence_matches {
415 return None;
416 }
417 correspondence_ids.push(left_entry.id);
418 }
419 Some(CrossLanguageRuleMatch {
420 rule: CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE,
421 correspondence_ids,
422 })
423}
424
425pub(super) const DIRECT_LOOP_SEQUENCE_CORRESPONDENCE_ID: &str = "direct-loop-sequence-v1";
430
431fn is_cross_language_direct_loop(graph: &SemanticOperationGraph) -> bool {
436 matches!(
437 graph.nodes.as_slice(),
438 [
439 OperationNode {
440 kind: OperationKind::Source,
441 attributes: source,
442 },
443 OperationNode {
444 kind: OperationKind::Collect | OperationKind::Reduce,
445 attributes: operation,
446 },
447 ] if source.api_names.is_empty()
448 && operation.api_names.is_empty()
449 && source.fallible_kind.is_none()
450 && operation.fallible_kind.is_none()
451 && source.direct_propagation.is_none()
452 && operation.direct_propagation.is_none()
453 && source.resource_kind.is_none()
454 && operation.resource_kind.is_none()
455 )
456}
457
458#[must_use]
463pub fn match_cross_language_optional_validation(
464 left: &SemanticOperationGraph,
465 right: &SemanticOperationGraph,
466) -> Option<CrossLanguageRuleMatch> {
467 let rust_and_cpp = matches!(
468 (left.language, right.language),
469 (Language::Rust, Language::Cpp) | (Language::Cpp, Language::Rust)
470 );
471 (CROSS_LANGUAGE_OPTIONAL_VALIDATION_RULE.scope == SemanticRuleScope::RustCpp
472 && left.schema_version == SOG_SCHEMA_VERSION
473 && right.schema_version == SOG_SCHEMA_VERSION
474 && rust_and_cpp
475 && left.build_variant_fingerprint != right.build_variant_fingerprint
476 && is_optional_validation(left)
477 && is_optional_validation(right))
478 .then_some(CrossLanguageRuleMatch {
479 rule: CROSS_LANGUAGE_OPTIONAL_VALIDATION_RULE,
480 correspondence_ids: vec![OPTIONAL_VALIDATION_CORRESPONDENCE_ID],
481 })
482}
483
484#[must_use]
489pub fn match_cross_language_result_validation(
490 left: &SemanticOperationGraph,
491 right: &SemanticOperationGraph,
492) -> Option<CrossLanguageRuleMatch> {
493 let rust_and_cpp = matches!(
494 (left.language, right.language),
495 (Language::Rust, Language::Cpp) | (Language::Cpp, Language::Rust)
496 );
497 (CROSS_LANGUAGE_RESULT_VALIDATION_RULE.scope == SemanticRuleScope::RustCpp
498 && left.schema_version == SOG_SCHEMA_VERSION
499 && right.schema_version == SOG_SCHEMA_VERSION
500 && rust_and_cpp
501 && left.build_variant_fingerprint != right.build_variant_fingerprint
502 && is_result_validation(left)
503 && is_result_validation(right))
504 .then_some(CrossLanguageRuleMatch {
505 rule: CROSS_LANGUAGE_RESULT_VALIDATION_RULE,
506 correspondence_ids: vec![RESULT_VALIDATION_CORRESPONDENCE_ID],
507 })
508}
509
510#[must_use]
514pub fn match_cross_language_result_direct_propagation(
515 left: &SemanticOperationGraph,
516 right: &SemanticOperationGraph,
517) -> Option<CrossLanguageRuleMatch> {
518 let rust_and_cpp = matches!(
519 (left.language, right.language),
520 (Language::Rust, Language::Cpp) | (Language::Cpp, Language::Rust)
521 );
522 (CROSS_LANGUAGE_RESULT_DIRECT_PROPAGATION_RULE.scope == SemanticRuleScope::RustCpp
523 && left.schema_version == SOG_SCHEMA_VERSION
524 && right.schema_version == SOG_SCHEMA_VERSION
525 && rust_and_cpp
526 && left.build_variant_fingerprint != right.build_variant_fingerprint
527 && is_result_direct_propagation(left)
528 && is_result_direct_propagation(right))
529 .then_some(CrossLanguageRuleMatch {
530 rule: CROSS_LANGUAGE_RESULT_DIRECT_PROPAGATION_RULE,
531 correspondence_ids: vec![RESULT_DIRECT_PROPAGATION_CORRESPONDENCE_ID],
532 })
533}
534
535const fn correspondence_covers(
536 correspondence: &CrossLanguageApiCorrespondence,
537 node: &OperationNode,
538) -> bool {
539 matches!(
540 (correspondence.operation, node.kind),
541 (OperationKind::Source, OperationKind::Source)
542 | (OperationKind::Filter, OperationKind::Filter)
543 | (OperationKind::Map, OperationKind::Map)
544 | (OperationKind::Reduce, OperationKind::Reduce)
545 | (OperationKind::Collect, OperationKind::Collect)
546 )
547}
548
549fn is_optional_validation(graph: &SemanticOperationGraph) -> bool {
550 OPTIONAL_VALIDATION_RULE.pattern.accepts(graph)
551 && matches!(
552 OPTIONAL_VALIDATION_RULE.matcher,
553 SemanticRuleMatcher::DirectConstruct {
554 kind,
555 fallible_kind,
556 direct_propagation,
557 } if direct_construct_matches(graph, kind, fallible_kind, direct_propagation)
558 )
559}
560
561fn is_result_validation(graph: &SemanticOperationGraph) -> bool {
562 RESULT_VALIDATION_RULE.pattern.accepts(graph)
563 && matches!(
564 RESULT_VALIDATION_RULE.matcher,
565 SemanticRuleMatcher::DirectConstruct {
566 kind,
567 fallible_kind,
568 direct_propagation,
569 } if direct_construct_matches(graph, kind, fallible_kind, direct_propagation)
570 )
571}
572
573fn is_result_direct_propagation(graph: &SemanticOperationGraph) -> bool {
574 CROSS_LANGUAGE_RESULT_DIRECT_PROPAGATION_RULE
575 .pattern
576 .accepts(graph)
577 && matches!(
578 CROSS_LANGUAGE_RESULT_DIRECT_PROPAGATION_RULE.matcher,
579 SemanticRuleMatcher::DirectConstruct {
580 kind,
581 fallible_kind,
582 direct_propagation,
583 } if direct_construct_matches(graph, kind, fallible_kind, direct_propagation)
584 )
585}