1use super::{
2 CROSS_LANGUAGE_OPTIONAL_VALIDATION_RULE, CROSS_LANGUAGE_RESULT_DIRECT_PROPAGATION_RULE,
3 CROSS_LANGUAGE_RESULT_VALIDATION_RULE, CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE,
4 DirectPropagation, FallibleKind, OperationAttributes, OperationEdge, OperationEdgeKind,
5 OperationKind, OperationNode, SOG_SCHEMA_VERSION, SemanticOperationGraph, TypeTag,
6};
7
8#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct SemanticRule {
11 pub id: &'static str,
13 pub version: u32,
15 pub confidence: f64,
17 pub scope: SemanticRuleScope,
19 pub pattern: SemanticRulePattern,
21 pub matcher: SemanticRuleMatcher,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum SemanticRuleScope {
32 SameBuildVariant,
34 RustCpp,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub struct SemanticRulePattern {
45 pub minimum_operations: usize,
47 pub permitted_kinds: &'static [OperationKind],
49}
50
51impl SemanticRulePattern {
52 #[must_use]
54 pub fn accepts(self, graph: &SemanticOperationGraph) -> bool {
55 graph.nodes.len() >= self.minimum_operations
56 && graph
57 .nodes
58 .iter()
59 .all(|node| self.permitted_kinds.contains(&node.kind))
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum SemanticRuleMatcher {
71 EquivalentSequence,
74 ExactApiSequence {
78 api_names: &'static [&'static str],
80 },
81 DirectConstruct {
84 kind: OperationKind,
86 fallible_kind: FallibleKind,
88 direct_propagation: Option<DirectPropagation>,
90 },
91 ResourceLifecycle,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq)]
98pub struct RuleMatch {
99 pub rule: SemanticRule,
101}
102
103const SEQUENCE_PIPELINE_RULE: SemanticRule = SemanticRule {
104 id: "sequence-pipeline-v1",
105 version: 1,
106 confidence: 0.7,
107 scope: SemanticRuleScope::SameBuildVariant,
108 pattern: SemanticRulePattern {
109 minimum_operations: 2,
110 permitted_kinds: &[
111 OperationKind::Source,
112 OperationKind::Filter,
113 OperationKind::Map,
114 OperationKind::Reduce,
115 OperationKind::Collect,
116 ],
117 },
118 matcher: SemanticRuleMatcher::EquivalentSequence,
119};
120
121const RUST_SERIALIZATION_ROUND_TRIP_RULE: SemanticRule = SemanticRule {
126 id: "rust-serialization-round-trip-v1",
127 version: 1,
128 confidence: 0.8,
129 scope: SemanticRuleScope::SameBuildVariant,
130 pattern: SemanticRulePattern {
131 minimum_operations: 2,
132 permitted_kinds: &[OperationKind::Map],
133 },
134 matcher: SemanticRuleMatcher::ExactApiSequence {
135 api_names: &["rust::ToString::to_string", "rust::str::parse"],
136 },
137};
138
139const CPP_SERIALIZATION_ROUND_TRIP_RULE: SemanticRule = SemanticRule {
144 id: "cpp-serialization-round-trip-v1",
145 version: 1,
146 confidence: 0.8,
147 scope: SemanticRuleScope::SameBuildVariant,
148 pattern: SemanticRulePattern {
149 minimum_operations: 2,
150 permitted_kinds: &[OperationKind::Map],
151 },
152 matcher: SemanticRuleMatcher::ExactApiSequence {
153 api_names: &["std::to_string", "std::stoull"],
154 },
155};
156
157const RESULT_DIRECT_PROPAGATION_RULE: SemanticRule = SemanticRule {
158 id: "result-direct-propagation-v1",
159 version: 1,
160 confidence: 0.95,
161 scope: SemanticRuleScope::SameBuildVariant,
162 pattern: SemanticRulePattern {
163 minimum_operations: 1,
164 permitted_kinds: &[OperationKind::PropagateError],
165 },
166 matcher: SemanticRuleMatcher::DirectConstruct {
167 kind: OperationKind::PropagateError,
168 fallible_kind: FallibleKind::Result,
169 direct_propagation: Some(DirectPropagation::ResultAdapter),
170 },
171};
172
173const OPTION_DIRECT_PROPAGATION_RULE: SemanticRule = SemanticRule {
174 id: "option-direct-propagation-v1",
175 version: 1,
176 confidence: 0.95,
177 scope: SemanticRuleScope::SameBuildVariant,
178 pattern: SemanticRulePattern {
179 minimum_operations: 1,
180 permitted_kinds: &[OperationKind::PropagateError],
181 },
182 matcher: SemanticRuleMatcher::DirectConstruct {
183 kind: OperationKind::PropagateError,
184 fallible_kind: FallibleKind::Option,
185 direct_propagation: Some(DirectPropagation::OptionAdapter),
186 },
187};
188
189pub(super) const OPTIONAL_VALIDATION_RULE: SemanticRule = SemanticRule {
190 id: "optional-validation-v1",
191 version: 1,
192 confidence: 0.85,
193 scope: SemanticRuleScope::SameBuildVariant,
194 pattern: SemanticRulePattern {
195 minimum_operations: 1,
196 permitted_kinds: &[OperationKind::Validate],
197 },
198 matcher: SemanticRuleMatcher::DirectConstruct {
199 kind: OperationKind::Validate,
200 fallible_kind: FallibleKind::Option,
201 direct_propagation: None,
202 },
203};
204
205pub(super) const RESULT_VALIDATION_RULE: SemanticRule = SemanticRule {
206 id: "result-validation-v1",
207 version: 1,
208 confidence: 0.85,
209 scope: SemanticRuleScope::SameBuildVariant,
210 pattern: SemanticRulePattern {
211 minimum_operations: 1,
212 permitted_kinds: &[OperationKind::Validate],
213 },
214 matcher: SemanticRuleMatcher::DirectConstruct {
215 kind: OperationKind::Validate,
216 fallible_kind: FallibleKind::Result,
217 direct_propagation: None,
218 },
219};
220
221const RESOURCE_LIFECYCLE_RULE: SemanticRule = SemanticRule {
222 id: "resource-lifecycle-v1",
223 version: 1,
224 confidence: 0.9,
225 scope: SemanticRuleScope::SameBuildVariant,
226 pattern: SemanticRulePattern {
227 minimum_operations: 2,
228 permitted_kinds: &[
229 OperationKind::AcquireResource,
230 OperationKind::ReleaseResource,
231 ],
232 },
233 matcher: SemanticRuleMatcher::ResourceLifecycle,
234};
235
236#[must_use]
242pub const fn registered_rules() -> &'static [SemanticRule] {
243 &[
244 RUST_SERIALIZATION_ROUND_TRIP_RULE,
245 CPP_SERIALIZATION_ROUND_TRIP_RULE,
246 SEQUENCE_PIPELINE_RULE,
247 RESULT_DIRECT_PROPAGATION_RULE,
248 OPTION_DIRECT_PROPAGATION_RULE,
249 OPTIONAL_VALIDATION_RULE,
250 RESULT_VALIDATION_RULE,
251 RESOURCE_LIFECYCLE_RULE,
252 CROSS_LANGUAGE_SEQUENCE_PIPELINE_RULE,
253 CROSS_LANGUAGE_OPTIONAL_VALIDATION_RULE,
254 CROSS_LANGUAGE_RESULT_VALIDATION_RULE,
255 CROSS_LANGUAGE_RESULT_DIRECT_PROPAGATION_RULE,
256 ]
257}
258
259#[must_use]
265pub fn match_registered_pipeline(
266 left: &SemanticOperationGraph,
267 right: &SemanticOperationGraph,
268) -> Option<RuleMatch> {
269 match_same_variant_rule(SEQUENCE_PIPELINE_RULE, left, right)
270}
271
272pub(super) fn match_same_variant_rule(
278 rule: SemanticRule,
279 left: &SemanticOperationGraph,
280 right: &SemanticOperationGraph,
281) -> Option<RuleMatch> {
282 if rule.scope != SemanticRuleScope::SameBuildVariant
283 || left.schema_version != SOG_SCHEMA_VERSION
284 || right.schema_version != SOG_SCHEMA_VERSION
285 || left.language != right.language
286 || left.build_variant_fingerprint != right.build_variant_fingerprint
287 || !rule.pattern.accepts(left)
288 || !rule.pattern.accepts(right)
289 {
290 return None;
291 }
292 let matches = match rule.matcher {
293 SemanticRuleMatcher::EquivalentSequence => {
294 left.nodes.len() == right.nodes.len()
295 && left
296 .nodes
297 .iter()
298 .any(|node| node.kind != OperationKind::Map)
299 && left
300 .nodes
301 .iter()
302 .zip(&right.nodes)
303 .all(|(left, right)| compatible_nodes(left, right))
304 }
305 SemanticRuleMatcher::ExactApiSequence { api_names } => {
306 left.nodes.len() == api_names.len()
307 && right.nodes.len() == api_names.len()
308 && left.nodes.iter().zip(&right.nodes).zip(api_names).all(
309 |((left, right), api_name)| {
310 compatible_nodes(left, right)
311 && left.attributes.api_names.len() == 1
312 && right.attributes.api_names.len() == 1
313 && left.attributes.api_names.contains(*api_name)
314 && right.attributes.api_names.contains(*api_name)
315 },
316 )
317 }
318 SemanticRuleMatcher::DirectConstruct {
319 kind,
320 fallible_kind,
321 direct_propagation,
322 } => {
323 direct_construct_matches(left, kind, fallible_kind, direct_propagation)
324 && direct_construct_matches(right, kind, fallible_kind, direct_propagation)
325 }
326 SemanticRuleMatcher::ResourceLifecycle => {
327 resource_lifecycle_matches(left) && resource_lifecycle_matches(right)
328 }
329 };
330 matches.then_some(RuleMatch { rule })
331}
332
333fn resource_lifecycle_matches(graph: &SemanticOperationGraph) -> bool {
334 matches!(
335 graph.nodes.as_slice(),
336 [OperationNode {
337 kind: OperationKind::AcquireResource,
338 attributes: OperationAttributes {
339 resource_kind: Some(acquired),
340 ..
341 },
342 }, OperationNode {
343 kind: OperationKind::ReleaseResource,
344 attributes: OperationAttributes {
345 resource_kind: Some(released),
346 ..
347 },
348 }] if acquired == released
349 ) && graph.edges.contains(&OperationEdge {
350 from: 0,
351 to: 1,
352 kind: OperationEdgeKind::ResourceLifetime,
353 })
354}
355
356#[must_use]
358pub fn match_registered_rule(
359 left: &SemanticOperationGraph,
360 right: &SemanticOperationGraph,
361) -> Option<RuleMatch> {
362 registered_rules()
363 .iter()
364 .copied()
365 .filter(|rule| rule.scope == SemanticRuleScope::SameBuildVariant)
366 .find_map(|rule| match_same_variant_rule(rule, left, right))
367}
368
369pub(super) fn only_api_name(node: &OperationNode) -> Option<&str> {
370 (node.attributes.api_names.len() == 1)
371 .then(|| node.attributes.api_names.iter().next())
372 .flatten()
373 .map(String::as_str)
374}
375
376pub(super) fn compatible_type_tags(left: Option<TypeTag>, right: Option<TypeTag>) -> bool {
377 match (left, right) {
378 (Some(left), Some(right)) => left == right,
379 _ => true,
380 }
381}
382
383pub(super) fn compatible_fallible_kinds(
384 left: Option<FallibleKind>,
385 right: Option<FallibleKind>,
386) -> bool {
387 match (left, right) {
388 (Some(left), Some(right)) => left == right,
389 _ => true,
390 }
391}
392
393fn compatible_nodes(left: &OperationNode, right: &OperationNode) -> bool {
405 left.kind == right.kind
406 && compatible_type_tags(left.attributes.type_tag, right.attributes.type_tag)
407 && compatible_fallible_kinds(
408 left.attributes.fallible_kind,
409 right.attributes.fallible_kind,
410 )
411}
412
413pub(super) fn direct_construct_matches(
414 graph: &SemanticOperationGraph,
415 kind: OperationKind,
416 fallible_kind: FallibleKind,
417 direct_propagation: Option<DirectPropagation>,
418) -> bool {
419 matches!(
420 graph.nodes.as_slice(),
421 [OperationNode {
422 kind: node_kind,
423 attributes: OperationAttributes {
424 fallible_kind: node_fallible_kind,
425 direct_propagation: node_direct_propagation,
426 ..
427 },
428 }] if *node_kind == kind
429 && *node_fallible_kind == Some(fallible_kind)
430 && direct_propagation.is_none_or(|required| {
431 *node_direct_propagation == Some(required)
432 })
433 )
434}